1 <?php
2
3 /* --------------------------------------------------------------
4 OrdersApiV2Controller.inc.php 2016-03-07
5 Gambio GmbH
6 http://www.gambio.de
7 Copyright (c) 2016 Gambio GmbH
8 Released under the GNU General Public License (Version 2)
9 [http://www.gnu.org/licenses/gpl-2.0.html]
10 --------------------------------------------------------------
11 */
12
13 MainFactory::load_class('HttpApiV2Controller');
14
15 /**
16 * Class OrdersApiV2Controller
17 *
18 * Provides a gateway to the OrderWriteService and OrderReadService classes, which handle the shop
19 * order resources.
20 *
21 * @category System
22 * @package ApiV2Controllers
23 */
24 class OrdersApiV2Controller extends HttpApiV2Controller
25 {
26 /**
27 * Order write service.
28 *
29 * @var OrderWriteService
30 */
31 protected $orderWriteService;
32
33 /**
34 * Order read service.
35 *
36 * @var OrderReadService
37 */
38 protected $orderReadService;
39
40 /**
41 * Order JSON serializer.
42 *
43 * @var OrderJsonSerializer
44 */
45 protected $orderJsonSerializer;
46
47 /**
48 * Order list item JSON serializer.
49 *
50 * @var OrderListItemJsonSerializer
51 */
52 protected $orderListItemJsonSerializer;
53
54 /**
55 * Sub resources.
56 *
57 * @var array
58 */
59 protected $subresource;
60
61
62 /**
63 * Initializes API Controller
64 */
65 protected function __initialize()
66 {
67 $this->orderWriteService = StaticGXCoreLoader::getService('OrderWrite');
68 $this->orderReadService = StaticGXCoreLoader::getService('OrderRead');
69 $this->orderJsonSerializer = MainFactory::create('OrderJsonSerializer');
70 $this->orderListItemJsonSerializer = MainFactory::create('OrderListItemJsonSerializer');
71 $this->subresource = array(
72 'items' => 'OrdersItemsApiV2Controller',
73 'history' => 'OrdersHistoryApiV2Controller',
74 'totals' => 'OrdersTotalsApiV2Controller'
75 );
76 }
77
78
79 /**
80 * @api {post} /orders Create Order
81 * @apiVersion 2.1.0
82 * @apiName CreateOrder
83 * @apiGroup Orders
84 *
85 * @apiDescription
86 * This method enables the creation of a new order into the system. The order can be bound to an existing
87 * customer or be standalone as implemented in the OrderService. Make sure that you check the Order resource
88 * representation. To see an example usage take a look at `docs/REST/samples/order-service/create_order.php`.
89 *
90 * @apiParamExample {json} Request-Body
91 * {
92 * "id": 400210,
93 * "statusId": 1,
94 * "purchaseDate": "2015-11-06 12:22:39",
95 * "currencyCode": "EUR",
96 * "languageCode": "DE",
97 * "comment": "",
98 * "paymentType": {
99 * "title": "cod",
100 * "module": "cod"
101 * },
102 * "shippingType": {
103 * "title": "Pauschale Versandkosten (Standar",
104 * "module": "flat_flat"
105 * },
106 * "customer": {
107 * "id": 1,
108 * "number": "",
109 * "email": "admin@shop.de",
110 * "phone": "0421 - 22 34 678",
111 * "vatId": "",
112 * "status": {
113 * "id": 0,
114 * "name": "Admin",
115 * "image": "admin_status.gif",
116 * "discount": 0,
117 * "isGuest": false
118 * }
119 * },
120 * "addresses": {
121 * "customer": {
122 * "gender": "m",
123 * "firstname": "John",
124 * "lastname": "Doe",
125 * "company": "JD Company",
126 * "street": "Rotterstr 33",
127 * "suburb": "",
128 * "postcode": "28219",
129 * "city": "Bremen",
130 * "countryId": 81,
131 * "zoneId": 0,
132 * "b2bStatus": false
133 * },
134 * "billing": {
135 * "gender": "m",
136 * "firstname": "John",
137 * "lastname": "Doe",
138 * "company": "JD Company",
139 * "street": "Rotterstr 33",
140 * "suburb": "",
141 * "postcode": "28219",
142 * "city": "Bremen",
143 * "countryId": 81,
144 * "zoneId": 0,
145 * "b2bStatus": false
146 * },
147 * "delivery": {
148 * "gender": "m",
149 * "firstname": "John",
150 * "lastname": "Doe",
151 * "company": "JD Company",
152 * "street": "Rotterstr 33",
153 * "suburb": "",
154 * "postcode": "28219",
155 * "city": "Bremen",
156 * "countryId": 81,
157 * "zoneId": 0,
158 * "b2bStatus": false
159 * }
160 * },
161 * "items": [
162 * {
163 * "id": 1,
164 * "model": "12345-s-black",
165 * "name": "Ein Artikel",
166 * "quantity": 1,
167 * "price": 11,
168 * "finalPrice": 11,
169 * "tax": 19,
170 * "isTaxAllowed": true,
171 * "discount": 0,
172 * "shippingTimeInformation": "",
173 * "checkoutInformation": "Checkout information goes here ...",
174 * "attributes": [
175 * {
176 * "id": 1,
177 * "name": "Farbe",
178 * "value": "rot",
179 * "price": 0,
180 * "priceType": "+",
181 * "optionId": 1,
182 * "optionValueId": 1,
183 * "combisId": null
184 * }
185 * ],
186 * "downloadInformation": {
187 * "filename": "Dokument.pdf",
188 * "maxDaysAllowed": 5,
189 * "countAvailable": 14
190 * },
191 * "addonValues": {
192 * "productId": "2"
193 * }
194 * }
195 * ],
196 * "totals": [
197 * {
198 * "id": 1,
199 * "title": "Zwischensumme:",
200 * "value": 50,
201 * "valueText": "50,00 EUR",
202 * "class": "ot_subtotal",
203 * "sortOrder": 10
204 * }
205 * ],
206 * "statusHistory": [
207 * {
208 * "id": 1,
209 * "statusId": 1,
210 * "dateAdded": "2015-11-06 12:22:39",
211 * "comment": "",
212 * "customerNotified": true
213 * }
214 * ],
215 * "addonValues": {
216 * "customerIp": "",
217 * "downloadAbandonmentStatus": "0",
218 * "serviceAbandonmentStatus": "0",
219 * "ccType": "",
220 * "ccOwner": "",
221 * "ccNumber": "",
222 * "ccExpires": "",
223 * "ccStart": "",
224 * "ccIssue": "",
225 * "ccCvv": ""
226 * }
227 * }
228 *
229 * @apiParam {String} statusId Order status ID, use one of the existing statuses IDs.
230 * @apiParam {String} purchaseDate Must have the 'Y-m-d H:i:s' format.
231 * @apiParam {String} currencyCode Order's currency code, use one of the existing currency codes.
232 * @apiParam {String} languageCode Use one of the existing language codes.
233 * @apiParam {String} comment Order's comments.
234 * @apiParam {Object} paymentType Contains information about the payment type, use values that match with the
235 * shop's modules.
236 * @apiParam {String} paymentType.title The payment title.
237 * @apiParam {String} paymentType.module The payment module name.
238 * @apiParam {Object} shippingType Contains information about the shipping type, use values that match with the
239 * shop's modules.
240 * @apiParam {String} shippingType.title The shipping title.
241 * @apiParam {String} shippingType.module The shipping module name.
242 * @apiParam {Object} customer Contains the order's customer information.
243 * @apiParam {String} customer.number Customer's number (often referred as CID).
244 * @apiParam {String} customer.email Customer's email address.
245 * @apiParam {String} customer.phone Customer's telephone number.
246 * @apiParam {String} customer.vatId Customer's VAT ID number.
247 * @apiParam {Object} customer.status Contains information about the customer's status on the system.
248 * @apiParam {Number} customer.status.id The customer's status ID must be one of the existing statuses in the shop.
249 * @apiParam {String} customer.status.name The customer-status name.
250 * @apiParam {String} customer.status.image The customer-status image (check the value from the shop).
251 * @apiParam {Number} customer.status.discount The discount that is made to this customer status.
252 * @apiParam {Boolean} customer.status.isGuest Defines whether the customer is a guest.
253 * @apiParam {Object} addresses Contains the address information of the order. There are three different kind of
254 * addresses: customer, billing and delivery.
255 * @apiParam {Object} addresses.customer Contains the customer-address data.
256 * @apiParam {String} addresses.customer.gender The gender value can be either "m" or "f".
257 * @apiParam {String} addresses.customer.firstname First name of the address block.
258 * @apiParam {String} addresses.customer.lastname Last name of the address block.
259 * @apiParam {String} addresses.customer.company Company name of the address block.
260 * @apiParam {String} addresses.customer.street Street and number of the address block.
261 * @apiParam {String} addresses.customer.suburb Suburb of the address block.
262 * @apiParam {String} addresses.customer.postcode Postcode of the address block.
263 * @apiParam {String} addresses.customer.city City of the address block.
264 * @apiParam {String} addresses.customer.countryId Country ID of the address block. You can use the "countries"
265 * resource of the API to get the available countries.
266 * @apiParam {String} addresses.customer.zoneId Zone ID of the address block. You can use the "zones" resource of
267 * the API to get the available countries.
268 * @apiParam {Boolean} addresses.customer.b2bStatus Whether the customer has the b2bStatus.
269 * @apiParam {Object} addresses.billing{...} Contains the address block for the billing. It expects the same value
270 * types as the customer-address block. See the JSON example above.
271 * @apiParam {Object} addresses.delivery{...} Contains the address block for the billing. It expects the same value
272 * types as the customer-address block. See the JSON example above.
273 * @apiParam {Array} items Every order contains a list of order items which can also have their own attributes.
274 * @apiParam {String} items.model Item's model value.
275 * @apiParam {String} items.name Item's name value.
276 * @apiParam {Number} items.quantity Quantity of the purchase.
277 * @apiParam {Number} items.price The initial price of the order item.
278 * @apiParam {Number} items.finalPrice The final price of the order item.
279 * @apiParam {Number} items.tax The tax applied to the value.
280 * @apiParam {Boolean} items.isTaxAllowed Whether tax is allowed.
281 * @apiParam {Number} items.discount Percentage of the discount made for this order.
282 * @apiParam {String} items.shippingTimeInformation Include shipping information to the order.
283 * @apiParam {String} items.checkoutInformation Include checkout information to the order.
284 * @apiParam {Array} items.attributes Contains some attributes or properties of the order item. The difference
285 * between the attributes and the properties is that attributes must have the "optionId" and
286 * "optionValueId" values while properties must only have the "combisId" value. The properties system
287 * is still included as a fallback to old releases of the shop, so we will use the "attributes" term in
288 * this document.
289 * @apiParam {String} items.attributes.name Attribute's name.
290 * @apiParam {String} items.attributes.value Attribute's value.
291 * @apiParam {Number} items.attributes.price Give the attributes price.
292 * @apiParam {String} items.attributes.priceType Make sure that you use one of the existing price types of the
293 * shop.
294 * @apiParam {Number} items.attributes.optionId Only-attributes need this value.
295 * @apiParam {Number} items.attributes.optionValueId Only-attributes need this value.
296 * @apiParam {Number} items.attributes.combisId Only-properties need this value.
297 * @apiParam {Object} items.downloadInformation Contains the optional download information of an order item.
298 * @apiParam {String} items.downloadInformation.filename Contains a non empty filename.
299 * @apiParam {Number} items.downloadInformation.maxDaysAllowed Contains the number of days where downloads are
300 * possible.
301 * @apiParam {Number} items.downloadInformation.countAvailable Contains the number of possible downloads.
302 * @apiParam {Object} items.addonValues (Optional) Contains key value pairs of additional order item data.
303 * @apiParam {Array} totals Contains the order totals. The order totals are entries that display analytic
304 * information about the charges of the user.
305 * @apiParam {String} totals.title Order total's title.
306 * @apiParam {Number} totals.value The value stands for the money.
307 * @apiParam {String} totals.valueText String representation of the value containing the currency code.
308 * @apiParam {String} totals.class Internal order-total class. A list of possible values can be seen in the
309 * database once you create a complete order record.
310 * @apiParam {Number} totals.sortOrder Defines the order of the totals list as they are being displayed.
311 * @apiParam {Object} addonValues (Optional) Contains key value pairs of additional order data.
312 *
313 * @apiSuccess (Success 201) Response-Body If successful, this method returns a complete Order resource in the
314 * response body.
315 *
316 * @apiError 400-BadRequest The body of the request was empty.
317 * @apiErrorExample Error-Response
318 * HTTP/1.1 400 Bad Request
319 * {
320 * "code": 400,
321 * "status": "error",
322 * "message": "Order data were not provided."
323 * }
324 */
325 public function post()
326 {
327 if($this->_mapResponse($this->subresource))
328 {
329 return;
330 }
331
332 $orderJsonString = $this->api->request->getBody();
333
334 if(empty($orderJsonString))
335 {
336 throw new HttpApiV2Exception('Order data were not provided.', 400);
337 }
338
339 $order = $this->orderJsonSerializer->deserialize($orderJsonString);
340
341 if($order->getCustomerId() !== 0)
342 {
343 $orderId = $this->orderWriteService->createNewCustomerOrder(new IdType($order->getCustomerId()),
344 $order->getCustomerStatusInformation(),
345 new StringType($order->getCustomerNumber()),
346 new EmailStringType($order->getCustomerEmail()),
347 new StringType($order->getCustomerTelephone()),
348 new StringType($order->getVatIdNumber()),
349 $order->getCustomerAddress(),
350 $order->getBillingAddress(),
351 $order->getDeliveryAddress(),
352 $order->getOrderItems(),
353 $order->getOrderTotals(),
354 $order->getShippingType(),
355 $order->getPaymentType(),
356 $order->getCurrencyCode(),
357 $order->getLanguageCode(),
358 new StringType($order->getComment()),
359 new IdType($order->getStatusId()),
360 $order->getAddonValues());
361 }
362 else
363 {
364 $orderId = $this->orderWriteService->createNewStandaloneOrder(new StringType($order->getCustomerNumber()),
365 new EmailStringType($order->getCustomerEmail()),
366 new StringType($order->getCustomerTelephone()),
367 new StringType($order->getVatIdNumber()),
368 $order->getCustomerAddress(),
369 $order->getBillingAddress(),
370 $order->getDeliveryAddress(),
371 $order->getOrderItems(),
372 $order->getOrderTotals(),
373 $order->getShippingType(),
374 $order->getPaymentType(),
375 $order->getCurrencyCode(),
376 $order->getLanguageCode(),
377 new StringType($order->getComment()),
378 new IdType($order->getStatusId()),
379 $order->getAddonValues()
380
381 );
382 }
383
384 $storedOrder = $this->orderReadService->getOrderById(new IdType($orderId));
385
386 $response = $this->orderJsonSerializer->serialize($storedOrder, false);
387 $this->_linkResponse($response);
388 $this->_writeResponse($response, 201);
389 }
390
391
392 /**
393 * @api {put} /orders/:id Update Order
394 * @apiVersion 2.1.0
395 * @apiName UpdateOrder
396 * @apiGroup Orders
397 *
398 * @apiDescription
399 * Use this method to update an existing order record. It uses the complete order JSON resource so
400 * it might be useful to fetch it through a GET request, alter its values and PUT it back in order
401 * to perform the update operation. Take a look in the POST method for more detailed explanation on
402 * every resource property. To see an example usage take a look at
403 * `docs/REST/samples/order-service/update_order.php`
404 *
405 * @apiSuccess Response-Body If successful, this method returns the updated Order resource in the response body.
406 *
407 * @apiError 400-BadRequest The body of the request was empty or the order record ID was not provided or
408 * is invalid.
409 *
410 * @apiErrorExample Error-Response (Empty request body)
411 * HTTP/1.1 400 Bad Request
412 * {
413 * "code": 400,
414 * "status": "error",
415 * "message": "Order data were not provided."
416 * }
417 *
418 * @apiErrorExample Error-Response (Missing or invalid ID)
419 * HTTP/1.1 400 Bad Request
420 * {
421 * "code": 400,
422 * "status": "error",
423 * "message": "Order record ID was not provided or is invalid."
424 * }
425 */
426 public function put()
427 {
428 if(!isset($this->uri[1]) || !is_numeric($this->uri[1]))
429 {
430 throw new HttpApiV2Exception('Order record ID was not provided or is invalid: ' . gettype($this->uri[1]),
431 400);
432 }
433
434 if($this->_mapResponse($this->subresource))
435 {
436 return;
437 }
438
439 $orderJsonString = $this->api->request->getBody();
440
441 if(empty($orderJsonString))
442 {
443 throw new HttpApiV2Exception('Order data were not provided.', 400);
444 }
445
446 $orderId = new IdType($this->uri[1]);
447
448 // Ensure that the order has the correct order id of the request url
449 $orderJsonString = $this->_setJsonValue($orderJsonString, 'id', $orderId->asInt());
450
451 $order = $this->orderJsonSerializer->deserialize($orderJsonString,
452 $this->orderReadService->getOrderById($orderId));
453
454 $this->orderWriteService->updateOrder($order);
455
456 $response = $this->orderJsonSerializer->serialize($order, false);
457 $this->_linkResponse($response);
458 $this->_writeResponse($response, 200);
459 }
460
461
462 /**
463 * @api {patch} /orders/:id/status Update Order Status
464 * @apiVersion 2.1.0
465 * @apiName UpdateOrderStatus
466 * @apiGroup Orders
467 *
468 * @apiDescription
469 * Use this method if you want to update the status of an existing order and create an order history entry. The
470 * status history entry must also contain extra information as shown in the JSON example.
471 *
472 * @apiParamExample {json} Order Status History
473 * {
474 * "statusId": 1,
475 * "comment": "This is the entry comment",
476 * "customerNotified": false
477 * }
478 *
479 * @apiParam {Number} statusId The new status ID will also be set in the order record.
480 * @apiParam {String} comment Assign a comment to the status history entry.
481 * @apiParam {Boolean} customerNotified Defines whether the customer was notified by this change.
482 *
483 * @apiSuccess (200) Request-Body If successful, this method returns the complete order status history resource
484 * in the response body.
485 *
486 * @apiSuccessExample {json} Success-Response
487 * {
488 * "id": 984,
489 * "statusId": 3,
490 * "dateAdded": "2016-01-22 10:52:11",
491 * "comment": "This is the entry's comments",
492 * "customerNotified": true
493 * }
494 *
495 * @apiError 400-BadRequest Order data were not provided or order record ID was not provided or is invalid.
496 *
497 * @apiErrorExample Error-Response (Empty request body)
498 * HTTP/1.1 400 Bad Request
499 * {
500 * "code": 400,
501 * "status": "error",
502 * "message": "Order data were not provided."
503 * }
504 *
505 * @apiErrorExample Error-Response (Missing or invalid ID)
506 * HTTP/1.1 400 Bad Request
507 * {
508 * "code": 400,
509 * "status": "error",
510 * "message": "Order record ID was not provided or is invalid."
511 * }
512 */
513 public function patch()
514 {
515 if(!isset($this->uri[1]) || !is_numeric($this->uri[1]))
516 {
517 throw new HttpApiV2Exception('Order record ID was not provided or is invalid: ' . gettype($this->uri[1]),
518 400);
519 }
520
521 if(isset($this->uri[2]) && $this->uri[2] == 'status')
522 {
523 $orderJsonString = $this->api->request->getBody();
524
525 if(empty($orderJsonString))
526 {
527 throw new HttpApiV2Exception('Order data were not provided.', 400);
528 }
529
530 $orderId = new IdType($this->uri[1]);
531 $json = json_decode($orderJsonString);
532
533 $this->orderWriteService->updateOrderStatus($orderId, new IdType($json->statusId),
534 new StringType((string)$json->comment),
535 new BoolType($json->customerNotified));
536
537 $order = $this->orderReadService->getOrderById($orderId);
538 $orderStatusHistory = $order->getStatusHistory()->getArray();
539 /** @var OrderStatusHistoryListItem $lastStatusHistoryItem */
540 $lastStatusHistoryItem = array_pop($orderStatusHistory);
541
542 $response = $this->orderJsonSerializer->serializeOrderStatusHistoryListItem($lastStatusHistoryItem);
543 $this->_writeResponse($response, 200);
544 }
545 }
546
547
548 /**
549 * @api {delete} /orders/:id Delete Order
550 * @apiVersion 2.1.0
551 * @apiName DeleteOrder
552 * @apiGroup Orders
553 *
554 * @apiDescription
555 * Remove an entire Order record from the database. This method will also remove the order-items along with
556 * their attributes and the order-total records. To see an example usage take a look at
557 * `docs/REST/samples/order-service/remove_order.php`
558 *
559 * @apiExample {curl} Delete Order With ID = 400597
560 * curl -X DELETE --user admin@shop.de:12345 http://shop.de/api.php/v2/orders/400597
561 *
562 * @apiSuccessExample {json} Success-Response
563 * {
564 * "code": 200,
565 * "status": "success",
566 * "action": "delete",
567 * "resource": "Order",
568 * "orderId": 400597
569 * }
570 *
571 * @apiError 400-BadRequest The order ID value was invalid.
572 *
573 * @apiErrorExample Error-Response
574 * HTTP/1.1 400 Bad Request
575 * {
576 * "code": 400,
577 * "status": "error",
578 * "message": "Order record ID was not provided in the resource URL."
579 * }
580 *
581 */
582 public function delete()
583 {
584 // Check if record ID was provided.
585 if(!isset($this->uri[1]) || !is_numeric($this->uri[1]))
586 {
587 throw new HttpApiV2Exception('Order record ID was not provided in the resource URL.', 400);
588 }
589
590 if($this->_mapResponse($this->subresource))
591 {
592 return;
593 }
594
595 // Remove order record from database.
596 $this->orderWriteService->removeOrderById(new IdType($this->uri[1]));
597
598 // Return response JSON.
599 $response = array(
600 'code' => 200,
601 'status' => 'success',
602 'action' => 'delete',
603 'resource' => 'Order',
604 'orderId' => (int)$this->uri[1]
605 );
606
607 $this->_writeResponse($response);
608 }
609
610
611 /**
612 * @api {get} /orders/:id Get Orders
613 * @apiVersion 2.1.0
614 * @apiName GetOrder
615 * @apiGroup Orders
616 *
617 * @apiDescription
618 * Get multiple or a single order record through a GET request. This method supports all the GET parameters
619 * that are mentioned in the "Introduction" section of this documentation.
620 *
621 * Important: Whenever you make requests that will return multiple orders the response will contain a smaller
622 * version of each order record called order-list-item. This is done for better performance because the creation
623 * of a complete order record takes significant time (many objects are involved). If you still need the complete
624 * data of an order record you will have to make an extra GET request with the ID provided.
625 *
626 * @apiExample {curl} Get All Orders
627 * curl -i --user admin@shop.de:12345 http://shop.de/api.php/v2/orders
628 *
629 * @apiExample {curl} Get Order With ID = 400242
630 * curl -i --user admin@shop.de:12345 http://shop.de/api.php/v2/orders/400242
631 *
632 * @apiExample {curl} Search Orders
633 * curl -i --user admin@shop.de:12345 http://shop.de/api.php/v2/orders?q=DE
634 *
635 * @apiExample {curl} Get Order's Items
636 * curl -i --user admin@shop.de:12345 http://shop.de/api.php/v2/orders/400573/items
637 *
638 * @apiExample {curl} Get Order Item's Attributes
639 * curl -i --user admin@shop.de:12345 http://shop.de/api.php/v2/orders/400573/items/57/attributes
640 *
641 * @apiExample {curl} Get Orders Totals
642 * curl -i --user admin@shop.de:12345 http://shop.de/api.php/v2/orders/400573/totals
643 */
644 public function get()
645 {
646 if($this->_mapResponse($this->subresource))
647 {
648 return;
649 }
650
651 if(isset($this->uri[1]) && is_numeric($this->uri[1])) // Get Single Record
652 {
653 $orders = array($this->orderReadService->getOrderById(new IdType($this->uri[1])));
654 }
655 else if($this->api->request->get('q') !== null)
656 {
657 $orders = $this->orderReadService->getOrderListByKeyword(new StringType($this->api->request->get('q')))
658 ->getArray();
659 }
660 else
661 {
662 $orders = $this->orderReadService->getOrderList()->getArray();
663 }
664
665 $response = array();
666
667 foreach($orders as $order)
668 {
669 if($order instanceof OrderInterface)
670 {
671 $serialized = $this->orderJsonSerializer->serialize($order, false);
672 }
673 else
674 {
675 $serialized = $this->orderListItemJsonSerializer->serialize($order, false);
676 }
677
678 $response[] = $serialized;
679 }
680
681 $this->_paginateResponse($response);
682 $this->_sortResponse($response);
683 $this->_minimizeResponse($response);
684 $this->_linkResponse($response);
685
686 // Return single resource to client and not array.
687 if(isset($this->uri[1]) && is_numeric($this->uri[1]) && count($response) > 0)
688 {
689 $response = $response[0];
690 }
691
692 $this->_writeResponse($response);
693 }
694 }
695