1 <?php
2 /* --------------------------------------------------------------
3 CustomersApiController.inc.php 2016-03-07
4 Gambio GmbH
5 http://www.gambio.de
6 Copyright (c) 2016 Gambio GmbH
7 Released under the GNU General Public License (Version 2)
8 [http://www.gnu.org/licenses/gpl-2.0.html]
9 --------------------------------------------------------------
10 */
11
12 MainFactory::load_class('HttpApiV2Controller');
13
14 /**
15 * Class CustomersApiV2Controller
16 *
17 * @category System
18 * @package ApiV2Controllers
19 */
20 class CustomersApiV2Controller extends HttpApiV2Controller
21 {
22 /**
23 * @var CustomerWriteService
24 */
25 protected $customerWriteService;
26
27 /**
28 * @var CustomerReadService
29 */
30 protected $customerReadService;
31
32 /**
33 * @var CountryService
34 */
35 protected $countryService;
36
37 /**
38 * @var AddressBookSErvice
39 */
40 protected $addressService;
41
42 /**
43 * @var CustomerJsonSerializer
44 */
45 protected $customerJsonSerializer;
46
47 /**
48 * @var AddressJsonSerializer
49 */
50 protected $addressJsonSerializer;
51
52
53 /**
54 * Initializes API Controller
55 */
56 protected function __initialize()
57 {
58 $this->customerWriteService = StaticGXCoreLoader::getService('CustomerWrite');
59 $this->customerReadService = StaticGXCoreLoader::getService('CustomerRead');
60 $this->countryService = StaticGXCoreLoader::getService('Country');
61 $this->addressService = StaticGXCoreLoader::getService('AddressBook');
62 $this->customerJsonSerializer = MainFactory::create('CustomerJsonSerializer');
63 $this->addressJsonSerializer = MainFactory::create('AddressJsonSerializer');
64 }
65
66
67 /**
68 * @api {post} /customers Create Customer
69 * @apiVersion 2.1.0
70 * @apiName CreateCustomer
71 * @apiGroup Customers
72 *
73 * @apiDescription
74 * This method enables the creation of a new customer (whether registree or a guest). Additionally
75 * the user can provide new address information or just set the id of an existing one. Check the
76 * examples bellow. An example script to demonstrate the creation of a new customer is located under
77 * `./docs/REST/samples/customer-service/create_account.php` in the git clone, another one to demonstrate the
78 * creation of a guest customer is located under `./docs/REST/samples/customer-service/create_guest_account.php`.
79 *
80 * @apiParamExample {json} Registree (New Address)
81 * {
82 * "gender": "m",
83 * "firstname": "John",
84 * "lastname": "Doe",
85 * "dateOfBirth": "1985-02-13",
86 * "vatNumber": "0923429837942",
87 * "telephone": "2343948798345",
88 * "fax": "2093049283",
89 * "email": "customer@email.de",
90 * "password": "0123456789",
91 * "isGuest": false,
92 * "address": {
93 * "company": "Test Company",
94 * "street": "Test Street",
95 * "suburb": "Test Suburb",
96 * "postcode": "23983",
97 * "city": "Test City",
98 * "countryId": 81,
99 * "zoneId": 84,
100 * "b2bStatus": true
101 * }
102 * }
103 *
104 * @apiParamExample {json} Registree (Existing Address)
105 * {
106 * "gender": "m",
107 * "firstname": "John",
108 * "lastname": "Doe",
109 * "dateOfBirth": "1985-02-13",
110 * "vatNumber": "0923429837942",
111 * "telephone": "2343948798345",
112 * "fax": "2093049283",
113 * "email": "customer@email.de",
114 * "password": "0123456789",
115 * "isGuest": false,
116 * "addressId": 57
117 * }
118 *
119 *
120 * @apiParamExample {json} Guest (New Address)
121 * {
122 * "gender": "m",
123 * "firstname": "John",
124 * "lastname": "Doe",
125 * "dateOfBirth": "1985-02-13",
126 * "vatNumber": "0923429837942",
127 * "telephone": "2343948798345",
128 * "fax": "2093049283",
129 * "email": "customer@email.de",
130 * "isGuest": true,
131 * "address": {
132 * "company": "Test Company",
133 * "street": "Test Street",
134 * "suburb": "Test Suburb",
135 * "postcode": "23983",
136 * "city": "Test City",
137 * "countryId": 81,
138 * "zoneId": 84,
139 * "b2bStatus": false
140 * }
141 * }
142 *
143 * @apiParamExample {json} Guest (Existing Address)
144 * {
145 * "gender": "m",
146 * "firstname": "John",
147 * "lastname": "Doe",
148 * "dateOfBirth": "1985-02-13",
149 * "vatNumber": "0923429837942",
150 * "telephone": "2343948798345",
151 * "fax": "2093049283",
152 * "email": "customer@email.de",
153 * "isGuest": true,
154 * "addressId": 57
155 * }
156 * }
157 *
158 * @apiParam {string} gender Customer's gender, provide "m" for male and "f" for female.
159 * @apiParam {string} firstname Customer's first name.
160 * @apiParam {string} lastname Customer's last name.
161 * @apiParam {string} dateOfBirth Customer's date of birth in "yyyy-mm-dd" format.
162 * @apiParam {string} vatNumber Valid customer VAT number.
163 * @apiParam {string} telephone Customer's telephone number.
164 * @apiParam {string} fax Customer's fax number.
165 * @apiParam {string} email Valid email address for the customer.
166 * @apiParam {string} password (Optional) Customer's password, only registree records need this value.
167 * @apiParam {bool} isGuest Customer's record type, whether true if guest or false if not.
168 * @apiParam {int} addressId Provide a record ID if the address already exist in the database (otherwise omit this
169 * property).
170 * @apiParam {object} address (Optional) Contains the customer's address data, can be omitted if the "addressId" is
171 * provided.
172 * @apiParam {string} address.company Customer's company name.
173 * @apiParam {string} address.suburb Customer's suburb.
174 * @apiParam {string} address.postcode Customer's postcode.
175 * @apiParam {string} address.city Customer's city.
176 * @apiParam {int} address.countryId Must be a country ID registered in the shop database.
177 * @apiParam {int} address.zoneId The country zone ID, as registered in the shop database.
178 *
179 * @apiSuccess (Success 201) Response-Body If successful, this method returns a complete Customers resource in the
180 * response body.
181 *
182 * @apiError 409-Conflict The API will return this status code if the customer's email already exists in the
183 * database (only applies on registree records).
184 */
185 public function post()
186 {
187 $customerJsonString = $this->api->request->getBody();
188
189 if(empty($customerJsonString))
190 {
191 throw new HttpApiV2Exception('Customer data were not provided.', 400);
192 }
193
194 $customerJsonObject = json_decode($customerJsonString);
195
196 // Check if customer email already exists.
197 if(isset($customerJsonObject->email) && $customerJsonObject->type === 'registree'
198 && $this->customerReadService->registreeEmailExists($customerJsonObject->email)
199 )
200 {
201 throw new HttpApiV2Exception('Registree email address already exists in the database.', 409);
202 }
203
204 $country = $this->countryService->getCountryById(new IdType($customerJsonObject->address->countryId));
205 $zone = $this->countryService->getCountryZoneById(new IdType($customerJsonObject->address->zoneId));
206 if($customerJsonObject->addressId !== null)
207 {
208 $address = $this->addressService->findAddressById(new IdType((int)$customerJsonObject->addressId));
209
210 $addressBlock = MainFactory::create('AddressBlock', $address->getGender(), $address->getFirstname(),
211 $address->getLastname(), $address->getCompany(),
212 $address->getB2BStatus(), $address->getStreet(), $address->getSuburb(),
213 $address->getPostcode(), $address->getCity(), $address->getCountry(),
214 $address->getCountryZone());
215 }
216 else
217 {
218 $addressBlock = MainFactory::create('AddressBlock',
219 MainFactory::create('CustomerGender', $customerJsonObject->gender),
220 MainFactory::create('CustomerFirstname',
221 $customerJsonObject->firstname),
222 MainFactory::create('CustomerLastname', $customerJsonObject->lastname),
223 MainFactory::create('CustomerCompany',
224 $customerJsonObject->address->company),
225 MainFactory::create('CustomerB2BStatus',
226 $customerJsonObject->address->b2bStatus),
227 MainFactory::create('CustomerStreet',
228 $customerJsonObject->address->street),
229 MainFactory::create('CustomerSuburb',
230 $customerJsonObject->address->suburb),
231 MainFactory::create('CustomerPostcode',
232 $customerJsonObject->address->postcode),
233 MainFactory::create('CustomerCity', $customerJsonObject->address->city),
234 $country, $zone);
235 }
236
237 if($customerJsonObject->isGuest === true)
238 {
239 $customer = $this->customerWriteService->createNewGuest(MainFactory::create('CustomerEmail',
240 $customerJsonObject->email),
241 MainFactory::create('DateTime',
242 $customerJsonObject->dateOfBirth),
243 MainFactory::create('CustomerVatNumber',
244 $customerJsonObject->vatNumber),
245 MainFactory::create('CustomerCallNumber',
246 $customerJsonObject->telephone),
247 MainFactory::create('CustomerCallNumber',
248 $customerJsonObject->fax),
249 $addressBlock);
250 }
251 else
252 {
253 $customer = $this->customerWriteService->createNewRegistree(MainFactory::create('CustomerEmail',
254 $customerJsonObject->email),
255 MainFactory::create('CustomerPassword',
256 $customerJsonObject->password),
257 MainFactory::create('DateTime',
258 $customerJsonObject->dateOfBirth),
259 MainFactory::create('CustomerVatNumber',
260 $customerJsonObject->vatNumber),
261 MainFactory::create('CustomerCallNumber',
262 $customerJsonObject->telephone),
263 MainFactory::create('CustomerCallNumber',
264 $customerJsonObject->fax),
265 $addressBlock);
266 }
267
268 $response = $this->customerJsonSerializer->serialize($customer, false);
269 $this->_linkResponse($response);
270 $this->_locateResource('customers', (string)$customer->getId());
271 $this->_writeResponse($response, 201);
272 }
273
274
275 /**
276 * @api {put} /customers/:id Update Customer
277 * @apiVersion 2.1.0
278 * @apiName UpdateCustomer
279 * @apiGroup Customers
280 *
281 * @apiDescription
282 * This method will update the information of an existing customer record. You will
283 * need to provide all the customer information with the request (except from password
284 * and customer id). Also note that you only have to include the "addressId" property.
285 * An example script to demonstrate how to update the admin accounts telephone number
286 * is located under `./docs/REST/samples/customer-service/update_admin_telephone.php`
287 * in the git clone.
288 *
289 * @apiParamExample {json} Request-Body (Registree)
290 * {
291 * "number": "234982739",
292 * "gender": "m",
293 * "firstname": "John",
294 * "lastname": "Doe",
295 * "dateOfBirth": "1985-02-13",
296 * "vatNumber": "0923429837942",
297 * "vatNumberStatus": 0,
298 * "telephone": "2343948798345",
299 * "fax": "2093049283",
300 * "email": "customer@email.de",
301 * "statusId": 2,
302 * "isGuest": false,
303 * "addressId": 54
304 * }
305 *
306 * @apiParamExample {json} Request-Body (Guest)
307 * {
308 * "number": "234982739",
309 * "gender": "m",
310 * "firstname": "John",
311 * "lastname": "Doe",
312 * "dateOfBirth": "1985-02-13",
313 * "vatNumber": "0923429837942",
314 * "vatNumberStatus": true,
315 * "telephone": "2343948798345",
316 * "fax": "2093049283",
317 * "email": "customer@email.de",
318 * "statusId": 1,
319 * "isGuest": true,
320 * "addressId": 98
321 * }
322 *
323 * @apiSuccess Response-Body If successful, this method returns the updated customer resource in the response body.
324 *
325 * @apiError 400-BadRequest Customer record ID was not provided or is invalid.
326 * @apiError 400-BadRequest Customer data were not provided.
327 * @apiError 404-NotFound Customer record was not found.
328 * @apiError 409-Conflict The API will return this status code if the customer's email already exists in the
329 * database (only applies on registree records).
330 */
331 public function put()
332 {
333 if(!isset($this->uri[1]) || !is_numeric($this->uri[1]))
334 {
335 throw new HttpApiV2Exception('Customer record ID was not provided or is invalid: ' . gettype($this->uri[1]),
336 400);
337 }
338
339 $customerJsonString = $this->api->request->getBody();
340
341 if(empty($customerJsonString))
342 {
343 throw new HttpApiV2Exception('Customer data were not provided.', 400);
344 }
345
346 // Fetch existing customer record.
347 $customerId = (int)$this->uri[1];
348 $customers = $this->customerReadService->filterCustomers(array('customers_id' => $customerId));
349
350 if(empty($customers))
351 {
352 throw new HttpApiV2Exception('Customer record was not found.', 404);
353 }
354
355 $customer = array_shift($customers);
356
357 // Ensure that the customer has the correct customer id of the request url
358 $customerJsonString = $this->_setJsonValue($customerJsonString, 'id', $customerId);
359
360 // Apply provided values into it.
361 $customer = $this->customerJsonSerializer->deserialize($customerJsonString, $customer);
362
363 // Check if new email belongs to another customer.
364 $db = StaticGXCoreLoader::getDatabaseQueryBuilder();
365
366 $count = $db->get_where('customers', array(
367 'customers_email_address' => (string)$customer->getEmail(),
368 'customers_id <>' => (string)$customer->getId()
369 ))->num_rows();
370
371 if($count)
372 {
373 throw new HttpApiV2Exception('Provided email address is used by another customer: '
374 . (string)$customer->getEmail(), 409);
375 }
376
377 // Update record and respond to client.
378 $this->customerWriteService->updateCustomer($customer);
379 $response = $this->customerJsonSerializer->serialize($customer, false);
380 $this->_linkResponse($response);
381 $this->_writeResponse($response);
382 }
383
384
385 /**
386 * @api {delete} /customers/:id Delete Customer
387 * @apiVersion 2.1.0
388 * @apiName DeleteCustomer
389 * @apiGroup Customers
390 *
391 * @apiDescription
392 * Remove a customer record from the system. This method will always return success
393 * even if the customer does not exist (due to internal CustomerWriteService architecture
394 * decisions, which strive to avoid unnecessary failures).
395 * An example script to demonstrate how to delete a customer is located under
396 * `./docs/REST/samples/customer-service/remove_account.php` in the git clone.
397 *
398 * @apiExample {curl} Delete Customer with ID = 84
399 * curl -X DELETE --user admin@shop.de:12345 http://shop.de/api.php/v2/customers/84
400 *
401 * @apiSuccessExample {json} Success-Response
402 * {
403 * "code": 200,
404 * "status": "success",
405 * "action": "delete",
406 * "customerId": 84
407 * }
408 */
409 public function delete()
410 {
411 // Check if record ID was provided.
412 if(!isset($this->uri[1]) || !is_numeric($this->uri[1]))
413 {
414 throw new HttpApiV2Exception('Customer record ID was not provided in the resource URL.', 400);
415 }
416
417 $customerId = (int)$this->uri[1];
418
419 // Remove customer from database.
420 $this->customerWriteService->deleteCustomerById(new IdType($customerId));
421
422 // Return response JSON.
423 $response = array(
424 'code' => 200,
425 'status' => 'success',
426 'action' => 'delete',
427 'customerId' => $customerId
428 );
429
430 $this->_writeResponse($response);
431 }
432
433
434 /**
435 * @api {get} /customers/:id Get Customers
436 * @apiVersion 2.1.0
437 * @apiName GetCustomer
438 * @apiGroup Customers
439 *
440 * @apiDescription
441 * Get multiple or a single customer record through the GET method. This resource supports
442 * the following GET parameters as described in the first section of documentation: sorting
443 * minimization, search, pagination and links. Additionally you can filter customers by providing
444 * the GET parameter "type=guest" or "type=registree". Sort and pagination GET parameters do not
445 * apply when a single customer record is selected (e.g. api.php/v2/customers/84).
446 * An example script to demonstrate how to fetch customer data is located under
447 * `./docs/REST/samples/customer-service/get_admin_data.php` in the git clone
448 *
449 * **Important**:
450 * Currently the CustomerReadService does not support searching in address information of
451 * a customer.
452 *
453 * @apiExample {curl} Get All Customers
454 * curl -i --user admin@shop.de:12345 http://shop.de/api.php/v2/customers
455 *
456 * @apiExample {curl} Get Customer With ID = 982
457 * curl -i --user admin@shop.de:12345 http://shop.de/api.php/v2/customers/982
458 *
459 * @apiExample {curl} Get Guest Customers
460 * curl -i --user admin@shop.de:12345 http://shop.de/api.php/v2/customers?type=guest
461 *
462 * @apiExample {curl} Search Customers
463 * curl -i --user admin@shop.de:12345 http://shop.de/api.php/v2/customers?q=admin@shop.de
464 *
465 * @apiExample {curl} Get Customer Addresses
466 * curl -i --user admin@shop.de:12345 http://shop.de/api.php/v2/customers/57/addresses
467 *
468 * @apiError 404-NotFound Customer record could not be found.
469 * @apiError 400-BadRequest Invalid customer type filter provided (expected 'registree' or 'guest').
470 *
471 * @apiErrorExample Error-Response (Customer Not Found)
472 * HTTP/1.1 404 Not Found
473 * {
474 * "code": 404,
475 * "status": "error",
476 * "message": "Customer record could not be found."
477 * }
478 *
479 * @apiErrorExample Error-Response (Invalid Type Filter)
480 * HTTP/1.1 400 Bad Request
481 * {
482 * "code": 400,
483 * "status": "error",
484 * "message": "Invalid customer type filter provided, expected 'guest' or 'registree' and got: admin"
485 * }
486 */
487 public function get()
488 {
489 // Sub-Resource Customer addresses: api.php/v2/customers/:id/addresses
490 if(isset($this->uri[2]) && $this->uri[2] === 'addresses')
491 {
492 $this->_getCustomerAddresses();
493
494 return;
495 }
496
497 // Get Single Customer Record
498 if(isset($this->uri[1]) && is_numeric($this->uri[1]))
499 {
500 $customers = $this->customerReadService->filterCustomers(array('customers_id' => (int)$this->uri[1]));
501
502 if(empty($customers))
503 {
504 throw new HttpApiV2Exception('Customer record could not be found.', 404);
505 }
506 }
507 // Search Customer Records
508 else if($this->api->request->get('q') !== null)
509 {
510 $searchKey = '%' . $this->api->request->get('q') . '%';
511 $search = array(
512 'customers_cid LIKE ' => $searchKey,
513 'customers_vat_id LIKE ' => $searchKey,
514 'customers_gender LIKE ' => $searchKey,
515 'customers_firstname LIKE ' => $searchKey,
516 'customers_lastname LIKE ' => $searchKey,
517 'customers_dob LIKE ' => $searchKey,
518 'customers_email_address LIKE ' => $searchKey,
519 'customers_telephone LIKE ' => $searchKey,
520 'customers_fax LIKE ' => $searchKey
521 );
522
523 $customers = $this->customerReadService->filterCustomers($search);
524 }
525 // Filter customers by type ("guest" or "registree")
526 else if($this->api->request->get('type') !== null)
527 {
528 $type = $this->api->request->get('type');
529
530 if($type === 'guest')
531 {
532 $customers = $this->customerReadService->filterCustomers(array('account_type' => '1'));
533 }
534 else if($type === 'registree')
535 {
536 $customers = $this->customerReadService->filterCustomers(array('account_type' => '0'));
537 }
538 else
539 {
540 throw new HttpApiV2Exception('Invalid customer type filter provided, expected "guest" or "registree" and got: '
541 . $type, 400);
542 }
543 }
544 // Get all registered customer records without applying filters.
545 else
546 {
547 $customers = $this->customerReadService->filterCustomers();
548 }
549
550 // Prepare response data.
551 $response = array();
552 foreach($customers as $customer)
553 {
554 $response[] = $this->customerJsonSerializer->serialize($customer, false);
555 }
556
557 $this->_paginateResponse($response);
558 $this->_sortResponse($response);
559 $this->_minimizeResponse($response);
560 $this->_linkResponse($response);
561
562 // Return single resource to client and not array.
563 if(isset($this->uri[1]) && is_numeric($this->uri[1]) && count($response) > 0)
564 {
565 $response = $response[0];
566 }
567
568 $this->_writeResponse($response);
569 }
570
571
572 /**
573 * Sub-Resource Customer Addresses
574 *
575 * This method will return all the addresses of the required customer, providing a fast
576 * way to access relations between customers and addresses.
577 *
578 * @see CustomersApiV2Controller::get()
579 *
580 * @throws HttpApiV2Exception
581 */
582 protected function _getCustomerAddresses()
583 {
584 if(!isset($this->uri[1]) && is_numeric($this->uri[1]))
585 {
586 throw new HttpApiV2Exception('Invalid customer ID provided: ' . gettype($this->uri[1]), 400);
587 }
588
589 $customer = $this->customerReadService->getCustomerById(new IdType((int)$this->uri[1]));
590 $addresses = $this->addressService->getCustomerAddresses($customer);
591
592 $response = array();
593 foreach($addresses as $address)
594 {
595 $response[] = $this->addressJsonSerializer->serialize($address, false);
596 }
597
598 $this->_sortResponse($response);
599 $this->_paginateResponse($response);
600 $this->_minimizeResponse($response);
601 $this->_linkResponse($response);
602 $this->_writeResponse($response);
603 }
604 }