Cs Cart – Api support for multipart form-data
- Digital Engineering
Cs Cart – Api support for multipart form-data
Cscart 4+ has one great feature i.e API. There are some basic apis already written in cscart, like Products, Orders, Users etc. You can check these apis in detail here http://docs.cs-cart.com/4.1.x/api/index.html. Currently, Api’s in Cscart has only support for content type application/json and text.
Now suppose we are building a mobile app for our Web store and we need to upload product images from mobile app. But our api only support application/json format. If we are strict with current scenario then we need to send image content as binary format or base64 encoded format, which increase our request size which is not a good solution. We can also send Image path from any live store as cscart supports image upload from url and no need to change anything.
Best way to upload images is add support for content type multipart/form-data and modify or overwrite Product api . For that we need to understand existing api system. How request is processed and how response is generated.
What happen when any api request is initiated?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
Define API = true // so we can check anywhere that request is coming from api include init.php which call fn_init_api() hook. then it calls handleRequest() method of Api class. Here is a code snipt for fn_init_api() function fn_init_api() { Tygh::$app['api'] = new Api(); return array(INIT_STATUS_OK); } Api class class Api { public function __construct($formats = array('json', 'text')) { FormatManager::initiate($formats); $this->request = new Request(); if (!$this->protocolValidator()) { $response = new Response(Response::STATUS_FORBIDDEN, 'The API is only accessible over HTTPS'); $response->send(); } $this->defineArea(); if ($this->area == 'C' && !Registry::get('config.tweaks.api_allow_customer')) { $response = new Response(Response::STATUS_UNAUTHORIZED); $response->send(); } } } |
Api class constructor has format parameter which is a array of format it supports. And default value is array(‘json’, ‘text’). When cscart create a object of Api() class it pass nothing as parameter so default value is passed.So we have to change that by rewriting fn_init_api().
To recreate Api object we create our addon and in his init.php write this code
1 2 3 4 5 6 7 8 9 10 11 12 |
if (defined('API')) { fn_init_stack(array('fn_<addon-name>_init_api')); } Now in our addon's func.php create method fn_<addon-name>_init_api fn_<addon-name>_init_api(){ Tygh::$app['api'] = new Api(array('json','text','formdata')); return array(INIT_STATUS_OK); } |
Now we have fordata format for our request. We have to bind that format with multipart/form-data request. For that create a file at /app/Tygh/Api/Formats/Formdata.php and copy following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
<?php namespace Tygh\Api\Formats; use Tygh\Api\IFormat; class Formdata implements IFormat { protected $mime_types = array( 'multipart/form-data' ); public function getMimeTypes() { return $this->mime_types; } public function encode($data) { return json_encode($data); // resposnse is still in json format } public function decode($data) { $boundary = substr($data, 0, strpos($data, "\r\n")); // Fetch each part $parts = array_slice(explode($boundary, $data), 1); $result = array(); $nmValue = ""; foreach ($parts as $part) { // If this is the last part, break if ($part == "--\r\n") break; // Separate content from headers $part = ltrim($part, "\r\n"); list($raw_headers, $body) = explode("\r\n\r\n", $part, 2); // Parse the headers list $raw_headers = explode("\r\n", $raw_headers); $headers = array(); foreach ($raw_headers as $header) { list($name, $value) = explode(':', $header); $headers[strtolower($name)] = ltrim($value, ' '); } // Parse the Content-Disposition to get the field name, etc. if (isset($headers['content-disposition'])) { $filename = null; preg_match( '/^(.+); *name="([^"]+)"(; *filename="([^"]+)")?/', $headers['content-disposition'], $matches ); list(, $type, $name) = $matches; isset($matches[4]) and $filename = $matches[4]; if($filename){ $nmValue .= $name."[filename]=".$filename."&"; $nmValue .= $name."[file_content]=".urlencode(substr($body, 0, strlen($body) - 2))."&"; }else{ $nmValue .= $name."=".urlencode(substr($body, 0, strlen($body) - 2))."&"; } } } parse_str($nmValue,$n); $result = $n; return array($result); } } |
Now we have done with format. We add parser for request with content-type multipart/form-data. We just need to do is upload our image.
For that we need to create a version for our product api. Create a folder structure like this in our addon Tygh/Api/Entities/v30. Create file Products.php in this folder with following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
<?php namespace Tygh\Api\Entities\v30; use Tygh\Storage; use Tygh\Enum\ProductFeatures; use Tygh\Api\AEntity; use Tygh\Api\Response; use Tygh\Registry; class Products extends \Tygh\Api\Entities\Products { public function create($params) { $data = array(); $valid_params = true; $status = Response::STATUS_BAD_REQUEST; unset($params['product_id']); if (empty($params['category_ids'])) { $data['message'] = __('api_required_field', array( '[field]' => 'category_ids' )); $valid_params = false; } if (!isset($params['price'])) { $data['message'] = __('api_required_field', array( '[field]' => 'price' )); $valid_params = false; } if ($valid_params) { if (!is_array($params['category_ids'])) { $params['category_ids'] = fn_explode(',',$params['category_ids']); } $this->prepareFeature($params); //to read Image Data from input stream and save it to tmp folder $this->prepareImagesBefore($params); $this->prepareImages($params); $product_id = fn_update_product($params); if ($product_id) { $status = Response::STATUS_CREATED; $data = array( 'product_id' => $product_id, ); } } return array( 'status' => $status, 'data' => $data ); } public function update($id, $params) { $data = array(); $status = Response::STATUS_BAD_REQUEST; $lang_code = $this->safeGet($params, 'lang_code', DEFAULT_LANGUAGE); parent::prepareFeature($params); //to read Image Data from input stream and save it to tmp folder $this->prepareImagesBefore($params); parent::prepareImages($params, $id); $product_id = fn_update_product($params, $id, $lang_code); if ($product_id) { $status = Response::STATUS_OK; $data = array( 'product_id' => $product_id ); } return array( 'status' => $status, 'data' => $data ); } function prepareImagesBefore(&$params){ $tmp_path = Storage::instance('images')->getUrl('tmp'); $tmp_dir = DIR_ROOT."/images/tmp"; //print_r($params);die; if(!empty($params['images'])){ $i=0; foreach ($params['images'] as $image){ if($image['is_default'] == 1){ file_put_contents($tmp_dir."/".$image['image']['filename'], $image['image']['file_content']); $params['main_pair']['detailed']['image_path'] = $tmp_path."/".$image['image']['filename']; }else{ file_put_contents($tmp_dir."/".$image['image']['filename'], $image['image']['file_content']); $params['image_pairs'][$i]['detailed']['image_path'] = $tmp_path."/".$image['image']['filename']; $i++; } } } } } |
That’s it. Now we can use this api by this url http://domainname.com/api/3.0/products.
Related content
Auriga: Leveling Up for Enterprise Growth!
Auriga’s journey began in 2010 crafting products for India’s