OpenXE/www/pages/shopimporter_woocommerce.php

2354 lines
59 KiB
PHP

<?php
/*
**** COPYRIGHT & LICENSE NOTICE *** DO NOT REMOVE ****
*
* Xentral (c) Xentral ERP Sorftware GmbH, Fuggerstrasse 11, D-86150 Augsburg, * Germany 2019
*
* This file is licensed under the Embedded Projects General Public License *Version 3.1.
*
* You should have received a copy of this license from your vendor and/or *along with this file; If not, please visit www.wawision.de/Lizenzhinweis
* to obtain the text of the corresponding license version.
*
**** END OF COPYRIGHT & LICENSE NOTICE *** DO NOT REMOVE ****
*/
?>
<?php
use Xentral\Components\Http\JsonResponse;
use Xentral\Modules\Onlineshop\Data\OrderStatus;
use Xentral\Modules\Onlineshop\Data\OrderStatusUpdateRequest;
use Xentral\Components\Logger\Logger;
class Shopimporter_Woocommerce extends ShopimporterBase
{
// protected $canexport = false;
public $intern = false;
public $shopid;
public $data;
/**
* @var $client WCClient $client
*/
public $client;
public $url;
/** These variables hold the status strings WooCommerce is using to represent
* the order status. They can easily be adjusted by the user via the GUI.
*/
public $statusPending;
public $statusProcessing;
public $statusCompleted;
public $priceType;
/**
* Stores the preferences that the user can change when editing this shop importer
* TODO: Looks like this should be moved to ShopimporterBase
* Unlike the other shop importers this is stored as an object variable - we dont
* want every single function that needs some prefrences to init a new SQL query
* @var $preferences array
*/
protected $preferences;
private $protokoll;
/**
* @var Application
*/
protected $app;
protected $dump;
/** @var Logger $logger */
public $logger;
public function __construct($app, $intern = false)
{
$this->app=$app;
$this->intern = true;
$this->logger = $app->Container->get('Logger');
}
public function ImportList()
{
$msg = $this->app->erp->base64_url_encode('<div class="info">Sie k&ouml;nnen hier die Shops einstellen</div>');
header('Location: index.php?module=onlineshops&action=list&msg='.$msg);
exit;
}
/**
* This function returns the number of orders which have not yet been imported
*/
public function ImportGetAuftraegeAnzahl()
{
// Query the API to get new orders, filtered by the order status as specifed by the user.
// We set per_page to 100 - this could lead to a situation where there are more than
// 100 new Orders, but we still only return 100.
// Array containing additional settings, namely 'ab_nummer' (containting the next order number to get)
// and 'holeallestati' (an integer)
$tmp = $this->CatchRemoteCommand('data');
// Only orders having an order number greater or equal than this should be fetched. null otherwise
$number_from = empty($tmp['ab_nummer']) ? null : (int)$tmp['ab_nummer'];
// pending orders will be fetched into this array. it's length is returned at the end of the funciton
$pendingOrders = array();
if ($number_from) {
// Number-based import is selected
// The WooCommerce API doenst allow for a proper "greater than id n" request.
// we fake this behavior by creating an array that contains 'many' (~ 1000) consecutive
// ids that are greater than $from_number and use this array with the 'include' property
// of the WooCommerce API
$number_to = $number_from+800;
if(!empty($tmp['bis_nummer'])){
$number_to = $tmp['bis_nummer'];
}
$fakeGreaterThanIds = range($number_from, $number_to);
$pendingOrders = $this->client->get('orders', [
'per_page' => 100,
'include' => implode(",",$fakeGreaterThanIds),
]);
} else {
// fetch posts by status
$pendingOrders = $this->client->get('orders', [
'status' => array_map('trim',explode(';', $this->statusPending)),
'per_page' => 100
]);
}
return (!empty($pendingOrders)?count($pendingOrders):0);
}
/**
* Calling this function queries the api for pending orders and returns them
* as an array.
*
* TODO: Only one single order is returned per invocation of this function.
* Given that we have to perform an exteremly expensive external HTTP call
* every time we call this function and could easily process more than one
* order this seems very bad performance-wise.
*/
public function ImportGetAuftrag()
{
// Array containing additional settings, namely 'ab_nummer' (containting the next order number to get)
// and 'holeallestati' (an integer)
$tmp = $this->CatchRemoteCommand('data');
// Only orders having an order number greater or equal than this should be fetched. null otherwise
$number_from = empty($tmp['ab_nummer']) ? null : (int)$tmp['ab_nummer'];
// pending orders will be fetched into this array. it's length is returned at the end of the funciton
$pendingOrders = array();
if ($number_from) {
// Number-based import is selected
// The WooCommerce API doenst allow for a proper "greater than id n" request.
// we fake this behavior by creating an array that contains 'many' (~ 1000) consecutive
// ids that are greater than $from_number and use this array with the 'include' property
// of the WooCommerce API
$number_to = $number_from+800;
if(!empty($tmp['bis_nummer'])){
$number_to = $tmp['bis_nummer'];
}
$fakeGreaterThanIds = range($number_from, $number_to);
$pendingOrders = $this->client->get('orders', [
'per_page' => 20,
'include' => implode(',',$fakeGreaterThanIds),
'order' => 'asc',
'orderby' => 'id'
]);
} else {
// fetch posts by status
$pendingOrders = $this->client->get('orders', [
'status' => array_map('trim',explode(';', $this->statusPending)),
'per_page' => 20,
'order' => 'asc',
'orderby' => 'id'
]);
}
// Return an empty array in case there are no orders to import
if ((!empty($pendingOrders)?count($pendingOrders):0) === 0) {
return null;
}
$tmp = [];
foreach ($pendingOrders as $pendingOrder){
$wcOrder = $pendingOrder;
$order = $this->parseOrder($wcOrder);
if (is_null($wcOrder)) {
continue;
}
$tmp[] = [
'id' => $order['auftrag'],
'sessionid' => '',
'logdatei' => '',
'warenkorb' => base64_encode(serialize($order)),
];
}
return $tmp;
}
// This function searches the wcOrder for the specified WC Meta key
// and returns it if found, null otherise
public function get_wc_meta($wcOrder, $meta_key) {
$value = null;
foreach ($wcOrder->meta_data as $meta) {
if ($meta->key == $meta_key) {
$value = $meta->value;
break;
}
}
return $value;
}
// Parse the given WooCommerce order, return a Xentral array-represented order.
// Overload this method whenever additional attributes are required.
public function parseOrder($wcOrder) {
$order = array();
$order['auftragsdaten'] = $wcOrder;
$isBillingCompany = !self::emptyString(
$wcOrder->billing->company
);
$isShippingCompany = !self::emptyString(
$wcOrder->shipping->company
);
$seperateShippingAddress = !self::compareObjects(
$wcOrder->billing,
$wcOrder->shipping,
['first_name', 'second_name', 'company', 'address_1', 'address_2',
'city', 'state', 'postcode', 'country']
);
if ($isBillingCompany) {
$order['name'] = $wcOrder->billing->company;
$order['anrede'] = 'firma';
$order['ansprechpartner'] = $wcOrder->billing->first_name . ' ' . $wcOrder->billing->last_name;
} else {
$order['name'] = $wcOrder->billing->first_name . ' ' . $wcOrder->billing->last_name;
// Retrieve title from meta data
// This is not a standard WC Feature! Should work with the very popuplar "WooCommerce germanized" plugin though
$meta_title = $this->get_wc_meta($wcOrder, "_billing_title");
if (!is_null($meta_title)) {
$order['anrede'] = ($meta_title == 'mrs') ? 'frau' : 'herr';
}
}
if(!empty($wcOrder->subshop)){
$order['subshop'] = $wcOrder->subshop;
}
// General order properties and billing address
$order['auftrag'] = $wcOrder->id;
$order['order'] = json_decode(json_encode($wcOrder), true);
$order['strasse'] = $wcOrder->billing->address_1;
if(!empty($wcOrder->billing->address_2)){
$order['adresszusatz'] = $wcOrder->billing->address_2;
}
$order['plz'] = $wcOrder->billing->postcode;
$order['ort'] = $wcOrder->billing->city;
$order['land'] = $wcOrder->billing->country;
$order['email'] = $wcOrder->billing->email;
$order['telefon'] = $wcOrder->billing->phone;
$order['bestelldatum'] = $wcOrder->date_created;
$order['gesamtsumme'] = $wcOrder->total;
$order['transaktionsnummer'] = $wcOrder->transaction_id;
$order['onlinebestellnummer'] = $wcOrder->number;
$order['versandkostenbrutto'] = $wcOrder->shipping_total + $wcOrder->shipping_tax;
$order['internebemerkung'] = $wcOrder->customer_note;
if(!empty((string)$wcOrder->currency)){
$warenkorb['waehrung'] = (string)$wcOrder->currency;
}
//
//
// Coupon Codes
//
$discount_total = (float)$wcOrder->discount_total;
$discount_tax = (float)$wcOrder->discount_tax;
if($discount_total != 0) { // Discount was applied to this order
// Calculate coupon amount
if($discount_tax == 0) {
// Tax calculations are not enabled for any used coupon
$order['rabattnetto'] = -abs((float)$discount_total);
} else{
// At least one used coupon has tax calculations enabled
$order['rabattbrutto'] = -abs((float)$discount_total);
$order['rabattbrutto'] += -abs((float)$discount_tax);
}
// Set coupon name
$couponLine = $wcOrder->coupon_lines;
// Check if we have a valid coupon line just to be sure and set the coupon name
if($couponLine && is_array($couponLine) && isset($couponLine[0]) && (String)$couponLine[0]->code !== '') {
$order['rabattname'] = (String)$couponLine[0]->code;
}
}
$seperateShippingAddress = !self::compareObjects(
$wcOrder->billing,
$wcOrder->shipping,
['first_name', 'second_name', 'company', 'address_1', 'address_2',
'city', 'state', 'postcode', 'country']
);
if ($seperateShippingAddress) {
$order['abweichendelieferadresse'] = '1';
if ($isShippingCompany) {
$order['lieferadresse_name'] = $wcOrder->shipping->company;
$order['lieferadresse_ansprechpartner'] = $wcOrder->shipping->first_name . ' ' . $wcOrder->shipping->last_name;
} else {
$order['lieferadresse_name'] = $wcOrder->shipping->first_name . ' ' . $wcOrder->shipping->last_name;
}
$order['lieferadresse_strasse'] = $wcOrder->shipping->address_1;
if(!empty($wcOrder->shipping->address_2)){
$order['lieferadresse_adresszusatz'] = $wcOrder->shipping->address_2;
}
$order['lieferadresse_plz'] = $wcOrder->shipping->postcode;
$order['lieferadresse_ort'] = $wcOrder->shipping->city;
$order['lieferadresse_land'] = $wcOrder->shipping->country;
}
// VAT stuff
$vatId = $this->get_wc_meta($wcOrder, "_billing_ustid");
if (!is_null($vatId) && !self::emptyString($vatId)) {
$order['ustid'] = $vatId;
}
foreach ($wcOrder->line_items as $wcOrderItem){
$order['articlelist'][] = $this->parseItem($wcOrderItem);
}
$order['zahlungsweise'] = $wcOrder->payment_method;
$order['lieferung'] = $wcOrder->shipping_lines[0]->method_id;
return $order;
}
function parseItem($wcOrderItem){
// The WC API doesnt expose the net price of a single product in the get order endpoint.
// We could query each individual product and get the price, but that would result in a
// huge amount of HTTP requests.
//
// We could use the price_netto attribute in the order item, but we get a higher precision using
// this custom calculation as we have access to the exact `total_tax` amount.
// Passing the net value of the line eliminates rounding issues if the position happens to have large quantites
switch ($this->priceType){
case 'grosscalculated':
$priceValue = ((float)$wcOrderItem->subtotal + (float)$wcOrderItem->subtotal_tax) / (float)$wcOrderItem->quantity;
$priceType = 'price';
break;
case 'netcalculated':
default:
$priceType = 'price_netto';
$priceValue = (float)$wcOrderItem->subtotal / (float)$wcOrderItem->quantity;
break;
}
$orderItem = array();
$orderItem['articleid'] = $wcOrderItem->sku;
$orderItem['name'] = $wcOrderItem->name;
$orderItem[$priceType] = $priceValue;
$orderItem['quantity'] = $wcOrderItem->quantity;
// The item could be a variable product in which case we have to retrieve the sku of the variation product
if (!empty($wcOrderItem->variation_id)) {
$variation_product_sku = $this->getSKUByShopId($wcOrderItem->id,$wcOrderItem->variation_id);
if (!empty($variation_product_sku)) {
$orderItem['articleid'] = $variation_product_sku;
}
}
return $orderItem;
}
/**
* Sets the Order status to processing, meaning we've successfully imported
* the order into our DB. This prevents the order from beeing imported again.
*/
public function ImportDeleteAuftrag()
{
$orderId = $this->CatchRemoteCommand('data')['auftrag'];
if (!empty($orderId)) {
$this->client->put('orders/'.$orderId, [
'status' => $this->statusProcessing,
]);
}
return 'ok';
}
/**
* Updates the order status once payment and shipping are set to ok.
* Also updates the order with the shipping tracking code
* @return string
* @throws WCHttpClientException
*/
public function ImportUpdateAuftrag()
{
/** @var OrderStatusUpdateRequest $data */
$data = $this->CatchRemoteCommand('data');
if ($data->orderStatus !== OrderStatus::Completed)
return;
$trackingCode = $data->shipments[0]?->trackingNumber;
if (!empty($trackingCode)) {
$this->client->post('orders/'.$data->orderId.'/notes', [
'note' => 'Tracking Code: ' . $trackingCode
]);
$this->logger->info("WooCommerce Tracking Code Rückmeldung für Auftrag: ".$data->orderId,
[
'orderId' => $data->orderId,
'trackingCode' => $trackingCode
]
);
}
$updateData = [
'status' => $this->statusCompleted,
'meta_data' => [
[
'key' => 'tracking_code',
'value' => $data->shipments[0]?->trackingNumber
],
[
'key' => 'shipping_carrier',
'value' => $data->shipments[0]?->shippingMethod
]
],
];
$this->client->put('orders/'.$data->orderId, $updateData);
$this->logger->info("WooCommerce Statusrückmeldung 'completed' für Auftrag: ".$data->orderId,
[
'orderId' => $data->orderId,
'status' => $this->statusCompleted
]
);
return 'ok';
}
/**
* This function syncs the current stock to the remote WooCommerce shop
* @return int
* @throws WCHttpClientException
*/
public function ImportSendListLager()
{
$tmp = $this->CatchRemoteCommand('data');
$anzahl = 0;
$ctmp = (!empty($tmp)?count($tmp):0);
for($i=0;$i<$ctmp;$i++)
{
// Get important values from input data
$artikel = $tmp[$i]['artikel'];
if($artikel === 'ignore') {
continue;
}
$nummer = $tmp[$i]['nummer'];
if(!empty($tmp[$i]['artikelnummer_fremdnummern'][0]['nummer'])){
$nummer = $tmp[$i]['artikelnummer_fremdnummern'][0]['nummer'];
}
$lageranzahl = $tmp[$i]['anzahl_lager'];
$pseudolager = trim($tmp[$i]['pseudolager']);
$inaktiv = $tmp[$i]['inaktiv'];
$status ='publish';
// Do some computations, sanitize input
if($pseudolager !== ''){
$lageranzahl = $pseudolager;
}
if($tmp[$i]['ausverkauft']){
$lageranzahl = 0;
}
if($inaktiv){
$status = 'private';
}
// get the product id that WooCommerce uses to represent the current article
$remoteIdInformation = $this->getShopIdBySKU($nummer);
if (empty($remoteIdInformation['id'])) {
// The online shop doesnt know this article, write to log and continue with next product
$this->logger->error("WooCommerce Artikel $nummer wurde im Online-Shop nicht gefunden! Falsche Artikelnummer im Shop hinterlegt?");
continue;
}
// Sync settings to online store
$updateProductParams = [
'manage_stock' => true,
'status' => $status,
'stock_quantity' => $lageranzahl
// WooCommerce doesnt have a standard property for the other values, we're ignoring them
];
if($remoteIdInformation['isvariant']){
$result = $this->client->put('products/' . $remoteIdInformation['parent'].'/variations/'. $remoteIdInformation['id'], $updateProductParams);
}else{
$result = $this->client->put('products/' . $remoteIdInformation['id'], $updateProductParams);
}
$this->logger->error("WooCommerce Lagerzahlenübertragung für Artikel: $nummer / $remoteIdInformation[id] - Anzahl: $lageranzahl",
[
'result' => $result
]
);
$anzahl++;
}
return $anzahl;
}
public function ImportStorniereAuftrag() {
$orderId = $this->CatchRemoteCommand('data')['auftrag'];
if (!empty($orderId)) {
$this->client->put('orders/'.$orderId, [
'status' => 'cancelled',
]);
} else {
return 'failed';
}
return 'ok';
}
public function ImportSendList() {
$tmp = $this->catchRemoteCommand('data');
$anzahl = 0;
for($i=0;$i<(!empty($tmp)?count($tmp):0);$i++){
$artikel = $tmp[$i]['artikel'];
$nummer = $tmp[$i]['nummer'];
if(!empty($tmp[$i]['artikelnummer_fremdnummern'][0]['nummer'])){
$nummer = $tmp[$i]['artikelnummer_fremdnummern'][0]['nummer'];
}
$laststock = $tmp[$i]['restmenge'];
$inaktiv = $tmp[$i]['inaktiv'];
$shippingtime = $tmp[$i]['lieferzeitmanuell'];
$hersteller = $tmp[$i]['hersteller'];
$herstellerlink = $tmp[$i]['herstellerlink'];
$name_de = $tmp[$i]['name_de'];
$name_en = $tmp[$i]['name_en'];
$description = html_entity_decode($tmp[$i]['uebersicht_de']);
$description_en = html_entity_decode($tmp[$i]['uebersicht_en']);
$preis = $tmp[$i]['preis'];
$kurzbeschreibung = $tmp[$i]['kurztext_de'];
$weight_kg = $tmp[$i]['gewicht'];
$dim_length = $tmp[$i]['laenge'];
$dim_width = $tmp[$i]['breite'];
$dim_height = $tmp[$i]['hoehe'];
// Sanitize dimensions
if (self::emptyString($weight_kg))
$weight_kg = null;
if (self::emptyString($dim_length))
$dim_length = null;
if (self::emptyString($dim_width))
$dim_width = null;
if (self::emptyString($dim_height))
$dim_height = null;
$meta_desc = $tmp[$i]['metadescription_de'];
$meta_title = $tmp[$i]['metatitle_de'];
$pseudopreis = $tmp[$i]['pseudopreis'];//*1.19;
if($pseudopreis <= $preis)$pseudopreis = $preis;
$steuersatz = $tmp[$i]['steuersatz'];
if($steuersatz > 1.10){
$steuersatz = 'normal';
}
else{
$steuersatz = 'ermaessigt';
}
$lageranzahl = $tmp[$i]['anzahl_lager'];
$pseudolager = trim($tmp[$i]['pseudolager']);
if($pseudolager > 0) $lageranzahl=$pseudolager;
if($tmp[$i]['ausverkauft']=="1"){
$lageranzahl=0; $laststock="1";
}
if($inaktiv)$aktiv=0;
else $aktiv=1;
$product_id = 0;
if($laststock!="1") $laststock=0;
$remoteIdInformation = $this->getShopIdBySKU($nummer);
$product_id = $remoteIdInformation['id'];
$commonMetaData = [
['key' => '_yoast_wpseo_metadesc', 'value' => $meta_desc],
['key' => '_yoast_wpseo_title', 'value' => $meta_title],
];
// Attributes that are used for both updating an existing product as well as creating a new one
$commonProductAtts = [
'name' => $name_de,
'description' => $description,
'status'=>($aktiv?'publish':'private'),
'regular_price' => number_format($pseudopreis,2,'.',''),
'sale_price' => number_format($preis,2,'.',''),
'short_description' => $kurzbeschreibung,
'weight' => $weight_kg,
'dimensions' => [
'length' => $dim_length,
'width' => $dim_width,
'height' => $dim_height
],
'meta_data' => $commonMetaData,
];
if($lageranzahl===0){
$commonProductAtts['stock_status'] = 'outofstock';
$commonProductAtts['manage_stock'] = true;
}
elseif($lageranzahl===''){
$commonProductAtts['stock_status'] = 'instock';
$commonProductAtts['manage_stock'] = false;
}
else{
$commonProductAtts['stock_status'] = 'instock';
$commonProductAtts['manage_stock'] = true;
}
if($lageranzahl!=='') {
$commonProductAtts['stock_quantity'] = (int)$lageranzahl;
}
if(!is_null($product_id)) {
// Such a product already appears to exist, so we update it
$this->client->put('products/'.$product_id, array_merge([
], $commonProductAtts));
$this->logger->info("WooCommerce Artikel geändert für Artikel: $nummer / $product_id");
}
else{
// create a new product
$product_id = $this->client->post('products/', array_merge([
'sku' => $nummer,
], $commonProductAtts))->id;
$this->logger->info("WooCommerce neuer Artikel angelegt: $nummer");
}
// TODO: Kategoriebaum und Bilder werden noch nicht uebertragen
// if(isset($tmp[$i]['kompletter_kategorienbaum'])){
// $baum = $tmp[$i]['kompletter_kategorienbaum'];
// $this->updateKategorieBaum($baum);
// }
// if(isset($tmp[$i]['Dateien'])){
// $dateien = $tmp[$i]['Dateien'];
// $this->save_images($dateien, $product_id);
// }
// Update the associated product categories
$chosenCats = array();
if(isset($tmp[$i]['kategorien']) || isset($tmp[$i]['kategoriename'])){
$kategorien = $tmp[$i]['kategorien'];
if (!($kategorien) && !self::emptyString($tmp[$i]['kategoriename'])) {
$kategorien = array(
array(
'name' => $tmp[$i]['kategoriename'],
)
);
}
if((!empty($kategorien)?count($kategorien):0)>0){
// Retrive all WC categories via API
$allWooCommerceCategories = $this->client->get('products/categories', ['per_page' => '100']);
$searchWpCategories = [];
foreach($allWooCommerceCategories as $a){
$searchWpCategories[$a->id] = $a->name;
}
// searchWPCategories is an assoc array of type WCCatId(Int) -> WCCatName(string)
// Iterate over the categories that are choosen in xentral
foreach($kategorien as $k => $v){
$wawi_cat_name = $v['name'];
$wcCatId = null;
// If WC has a matching category. We match based on name!
if(array_search($wawi_cat_name,array_values($searchWpCategories)) !== false) {
// get id of that WC Category
$wcCatId = array_search($wawi_cat_name,$searchWpCategories);
} else {
// No matching category exists
$wcCatId = $this->client->post('products/categories', [
'name' => $wawi_cat_name,
])->id;
}
if ($wcCatId) {
// update category. We first retrieve the product and append the new product category, not replace the entire category array.
$alreadyAssignedWCCats = $this->client->get('products/'.$product_id, [
'per_page' => 1,
])->categories;
// Get ids of existing categories
$existingCategoryIds = [];
foreach ($alreadyAssignedWCCats as $cat) {
$existingCategoryIds[] = $cat->id;
}
$allCatIds = array_merge($existingCategoryIds, array($wcCatId));
// prepare data to be in correct format for WC api. should be individual items with key 'id' and id as value
$allCatIdsWCAPIRep = array();
foreach($allCatIds as $id) {
$allCatIdsWCAPIRep[] = ['id' => $id];
}
// Update category assignment
$this->client->put('products/'.$product_id, [
'categories' => $allCatIdsWCAPIRep,
]);
$chosenCats[] = $wcCatId;
}
}
}
}
$anzahl++;
}
return $anzahl;
// return array($product_id,$anzahl,$nummer,$steuersatz, $preis);
}
/**
* Checks the connection to the WooCommerce API by trying a simple API request
*
* @return string
*/
public function ImportAuth()
{
try {
$orders = $this->client->get('orders', ['per_page' => '1']);
return 'success';
} catch (Exception $e) {
return 'failed: Keine Verbindung zur API - ' . $e->getMessage();
}
}
/**
* This is called by class.remote.php, initializes some class variables from the DB
* @param [type] $shopid [description]
* @param [type] $data [description]
* @return [type] [description]
* @throws WCHttpClientException
*/
public function getKonfig($shopid, $data)
{
$this->shopid = $shopid;
$this->data = $data;
$preferences_json = $this->app->DB->Select("SELECT einstellungen_json FROM shopexport WHERE id = '$this->shopid' LIMIT 1");
if($preferences_json){
$preferences = json_decode($preferences_json,true);
}
$this->protokoll = $preferences['felder']['protokoll'];
$ImportWooCommerceApiSecret = $preferences['felder']['ImportWoocommerceApiSecret'];
$ImportWooCommerceApiKey = $preferences['felder']['ImportWoocommerceApiKey'];
$ImportWooCommerceApiUrl = $preferences['felder']['ImportWoocommerceApiUrl'];
$this->statusPending = $preferences['felder']['statusPending'];
$this->statusProcessing = $preferences['felder']['statusProcessing'];
$this->statusCompleted = $preferences['felder']['statusCompleted'];
$this->priceType = $preferences['felder']['priceType'];
$this->url = $ImportWooCommerceApiUrl;
$this->client = new WCClient(
//URL des WooCommerce Rest Servers
$ImportWooCommerceApiUrl,
//WooCommerce API Key
$ImportWooCommerceApiKey,
//WooCommerce API Secret
$ImportWooCommerceApiSecret,
["query_string_auth" => true],
$this->logger
);
}
/**
* @param array $shopArr
* @param array $postData
*
* @return array
*/
public function updateShopexportArr($shopArr, $postData)
{
$shopArr['demomodus'] = 0;
$shopArr['anzgleichzeitig'] = 1;
$shopArr['cronjobaktiv'] = 1;
return $shopArr;
}
/**
* @return JsonResponse|null
*/
public function AuthByAssistent()
{
$ImportWooCommerceApiKey = $this->app->Secure->GetPOST('ImportWoocommerceApiKey');
$ImportWooCommerceApiSecret = $this->app->Secure->GetPOST('ImportWoocommerceApiSecret');
$ImportWooCommerceApiUrl = $this->app->Secure->GetPOST('ImportWoocommerceApiUrl');
if(empty($ImportWooCommerceApiUrl)) {
return new JsonResponse(['error' => 'Bitte die API-Url angeben'], JsonResponse::HTTP_BAD_REQUEST);
}
if(empty($ImportWooCommerceApiKey)) {
return new JsonResponse(['error' => 'Bitte den API-Key angeben'], JsonResponse::HTTP_BAD_REQUEST);
}
if(empty($ImportWooCommerceApiSecret)) {
return new JsonResponse(['error' => 'Bitte das API-Secret angeben'], JsonResponse::HTTP_BAD_REQUEST);
}
$this->client = new WCClient(
$ImportWooCommerceApiUrl,
$ImportWooCommerceApiKey,
$ImportWooCommerceApiSecret,
['query_string_auth' => true],
$this->logger
);
$auth = $this->ImportAuth();
if ($auth !== 'success') {
return new JsonResponse(['error' => $auth], JsonResponse::HTTP_BAD_REQUEST);
}
return null;
}
/**
* @return array[]
*/
public function getCreateForm()
{
return [
[
'id' => 0,
'name' => 'urls',
'inputs' => [
[
'label' => 'API Url',
'type' => 'text',
'name' => 'ImportWoocommerceApiUrl',
'validation' => true,
],
],
],
[
'id' => 1,
'name' => 'username',
'inputs' => [
[
'label' => 'API Key',
'type' => 'text',
'name' => 'ImportWoocommerceApiKey',
'validation' => true,
],
],
],
[
'id' => 2,
'name' => 'password',
'inputs' => [
[
'label' => 'API Secret',
'type' => 'password',
'name' => 'ImportWoocommerceApiSecret',
'validation' => true,
],
],
],
];
}
/**
* Returns the WooCommerce Product Id of a product given the SKU (= Xentral arikelnummer)
*
* @param string $sku Artikelnummer
*
* @return array|null The WooCommerce product id of the given product, null if such a product does not exist
* @throws WCHttpClientException
*/
private function getShopIdBySKU($sku) {
// Retrieve the product with the given sku.
// Note: We limit the result set to 1 (per_page=1), so this doesnt work
// if there are multiple products with the same sku. should not happen in practice anyway
$product = $this->client->get('products', ['sku' => $sku, 'per_page' => 1]);
// We look at the first product in the array.
// We may get an empty array, in that case null is returned
if (isset($product[0])){
return [
'id' => $product[0]->id,
'parent' => $product[0]->parent_id,
'isvariant' => !empty($product[0]->parent_id)];
}
return null;
}
private function getSKUByShopId($articleid, $variationid) {
$product = $this->client->get("products/$articleid/variations/$variationid");
// We look at the first product in the array.
// We may get an empty array, in that case null is returned
if (!empty($product))
return $product->sku;
return null;
}
public function EinstellungenStruktur()
{
return
array(
'ausblenden'=>array('abholmodus'=>array('zeitbereich')),
'archiv'=>array('ab_nummer'),
'felder'=>array(
// 'protokoll'=>array('typ'=>'checkbox','bezeichnung'=>'Protokollierung im Logfile:'),
'ImportWoocommerceApiKey'=>array('typ'=>'text','bezeichnung'=>'{|API Key:','size'=>60),
'ImportWoocommerceApiSecret'=>array('typ'=>'text','bezeichnung'=>'{|API Secret|}:','size'=>60),
'ImportWoocommerceApiUrl'=>array('typ'=>'text','bezeichnung'=>'{|API Url|}:','size'=>40),
'statusPending'=>array('typ'=>'text','bezeichnung'=>'{|Statusname Bestellung offen|}:','size'=>40, 'default' => 'pending', 'info' => '({|ggfs. getrennt durch ";": pending;on-hold|})'),
'statusProcessing'=>array('typ'=>'text','bezeichnung'=>'{|Statusname Bestellung in Bearbeitung|}:','size'=>10, 'default' => 'processing'),
'statusCompleted'=>array('typ'=>'text','bezeichnung'=>'{|Statusname Bestellung fertig|}:','size'=>10, 'default' => 'completed'),
'priceType'=>array('typ'=>'select','bezeichnung'=>'{|Preisberechnungsgrundlage bei Auftragsimport|}','optionen'=>array('netcalculated'=>'{|Nettopreis zurückrechnen (Standard)|}','grosscalculated'=>'{|Bruttopreis zurückrechnen|}')),
));
}
/**
* Compares two Objects and returns true if every variable in items
* is the same in $a and $b
* @param Obj $a [description]
* @param Obj $b [description]
* @param array $items [description]
* @return Bool [description]
*/
protected static function compareObjects($a, $b, $items) {
foreach($items as $v) {
if (property_exists($a, $v) && property_exists($b, $v)) {
if ($a->$v != $b->$v) {
//print_r($v);
return false;
}
}
}
return true;
}
/**
* Returns true when the string entered is empty, after stripping whitespace
* @param String $string input
* @return String output
*/
protected static function emptyString($string) {
return (strlen(trim($string)) == 0);
}
}
class WCClient
{
/**
* WooCommerce REST API WCClient version.
*/
const VERSION = '3.0.0';
/**
* HttpClient instance.
*
* @var WCHttpClient
*/
public $http;
/** @var Logger $logger */
public $logger;
/**
* Initialize client.
*
* @param string $url Store URL.
* @param string $consumerKey Consumer key.
* @param string $consumerSecret Consumer secret.
* @param array $options WCOptions (version, timeout, verify_ssl).
*
* @throws WCHttpClientException
*/
public function __construct($url, $consumerKey, $consumerSecret, $options = [], $logger)
{
$this->http = new WCHttpClient($url, $consumerKey, $consumerSecret, $options, $logger);
$this->logger = $logger;
}
/**
* POST method.
*
* @param string $endpoint API endpoint.
* @param array $data WCRequest data.
*
* @throws WCHttpClientException
*
* @return array
*/
public function post($endpoint, $data)
{
return $this->http->request($endpoint, 'POST', $data);
}
/**
* PUT method.
*
* @param string $endpoint API endpoint.
* @param array $data WCRequest data.
*
* @throws WCHttpClientException
*
* @return array
*/
public function put($endpoint, $data)
{
return $this->http->request($endpoint, 'PUT', $data);
}
/**
* GET method.
*
* @param string $endpoint API endpoint.
* @param array $parameters WCRequest parameters.
*
* @throws WCHttpClientException
*
* @return array
*/
public function get($endpoint, $parameters = [])
{
return $this->http->request($endpoint, 'GET', [], $parameters);
}
/**
* DELETE method.
*
* @param string $endpoint API endpoint.
* @param array $parameters WCRequest parameters.
*
* @throws WCHttpClientException
*
* @return array
*/
public function delete($endpoint, $parameters = [])
{
return $this->http->request($endpoint, 'DELETE', [], $parameters);
}
/**
* OPTIONS method.
*
* @param string $endpoint API endpoint.
*
* @throws WCHttpClientException
*
* @return array
*/
public function options($endpoint)
{
return $this->http->request($endpoint, 'OPTIONS');
}
}
class WCResponse
{
/**
* WCResponse code.
*
* @var int
*/
private $code;
/**
* WCResponse headers.
*
* @var array
*/
private $headers;
/**
* WCResponse body.
*
* @var string
*/
private $body;
/**
* Initialize response.
*
* @param int $code WCResponse code.
* @param array $headers WCResponse headers.
* @param string $body WCResponse body.
*/
public function __construct($code = 0, $headers = [], $body = '')
{
$this->code = $code;
$this->headers = $headers;
$this->body = $body;
}
/**
* Set code.
*
* @param int $code WCResponse code.
*/
public function setCode($code)
{
$this->code = (int) $code;
}
/**
* Set headers.
*
* @param array $headers WCResponse headers.
*/
public function setHeaders($headers)
{
$this->headers = $headers;
}
/**
* Set body.
*
* @param string $body WCResponse body.
*/
public function setBody($body)
{
$this->body = $body;
}
/**
* Get code.
*
* @return int
*/
public function getCode()
{
return $this->code;
}
/**
* Get headers.
*
* @return array $headers WCResponse headers.
*/
public function getHeaders()
{
return $this->headers;
}
/**
* Get body.
*
* @return string $body WCResponse body.
*/
public function getBody()
{
return $this->body;
}
}
class WCOptions
{
/**
* Default WooCommerce REST API version.
*/
const VERSION = 'wc/v3';
/**
* Default request timeout.
*/
const TIMEOUT = 30;
/**
* Default WP API prefix.
* Including leading and trailing slashes.
*/
const WP_API_PREFIX = '/wp-json/';
/**
* Default User Agent.
* No version number.
*/
const USER_AGENT = 'WooCommerce API Client-PHP';
/**
* WCOptions.
*
* @var array
*/
private $options;
/**
* Initialize HTTP client options.
*
* @param array $options Client options.
*/
public function __construct($options)
{
$this->options = $options;
}
/**
* Get API version.
*
* @return string
*/
public function getVersion()
{
return isset($this->options['version']) ? $this->options['version'] : self::VERSION;
}
/**
* Check if need to verify SSL.
*
* @return bool
*/
public function verifySsl()
{
return isset($this->options['verify_ssl']) ? (bool) $this->options['verify_ssl'] : true;
}
/**
* Get timeout.
*
* @return int
*/
public function getTimeout()
{
return isset($this->options['timeout']) ? (int) $this->options['timeout'] : self::TIMEOUT;
}
/**
* Basic Authentication as query string.
* Some old servers are not able to use CURLOPT_USERPWD.
*
* @return bool
*/
public function isQueryStringAuth()
{
return isset($this->options['query_string_auth']) ? (bool) $this->options['query_string_auth'] : false;
}
/**
* Check if is WP REST API.
*
* @return bool
*/
public function isWPAPI()
{
return isset($this->options['wp_api']) ? (bool) $this->options['wp_api'] : true;
}
/**
* Custom API Prefix for WP API.
*
* @return string
*/
public function apiPrefix()
{
return isset($this->options['wp_api_prefix']) ? $this->options['wp_api_prefix'] : self::WP_API_PREFIX;
}
/**
* oAuth timestamp.
*
* @return string
*/
public function oauthTimestamp()
{
return isset($this->options['oauth_timestamp']) ? $this->options['oauth_timestamp'] : \time();
}
/**
* Custom user agent.
*
* @return string
*/
public function userAgent()
{
return isset($this->options['user_agent']) ? $this->options['user_agent'] : self::USER_AGENT;
}
/**
* Get follow redirects
*
* @return bool
*/
public function getFollowRedirects()
{
return isset($this->options['follow_redirects']) ? (bool) $this->options['follow_redirects'] : false;
}
}
class WCRequest
{
/**
* WCRequest url.
*
* @var string
*/
private $url;
/**
* WCRequest method.
*
* @var string
*/
private $method;
/**
* WCRequest paramenters.
*
* @var array
*/
private $parameters;
/**
* WCRequest headers.
*
* @var array
*/
private $headers;
/**
* WCRequest body.
*
* @var string
*/
private $body;
/**
* Initialize request.
*
* @param string $url WCRequest url.
* @param string $method WCRequest method.
* @param array $parameters WCRequest paramenters.
* @param array $headers WCRequest headers.
* @param string $body WCRequest body.
*/
public function __construct($url = '', $method = 'POST', $parameters = [], $headers = [], $body = '')
{
$this->url = $url;
$this->method = $method;
$this->parameters = $parameters;
$this->headers = $headers;
$this->body = $body;
}
/**
* Set url.
*
* @param string $url WCRequest url.
*/
public function setUrl($url)
{
$this->url = $url;
}
/**
* Set method.
*
* @param string $method WCRequest method.
*/
public function setMethod($method)
{
$this->method = $method;
}
/**
* Set parameters.
*
* @param array $parameters WCRequest paramenters.
*/
public function setParameters($parameters)
{
$this->parameters = $parameters;
}
/**
* Set headers.
*
* @param array $headers WCRequest headers.
*/
public function setHeaders($headers)
{
$this->headers = $headers;
}
/**
* Set body.
*
* @param string $body WCRequest body.
*/
public function setBody($body)
{
$this->body = $body;
}
/**
* Get url.
*
* @return string
*/
public function getUrl()
{
return $this->url;
}
/**
* Get method.
*
* @return string
*/
public function getMethod()
{
return $this->method;
}
/**
* Get parameters.
*
* @return array
*/
public function getParameters()
{
return $this->parameters;
}
/**
* Get headers.
*
* @return array
*/
public function getHeaders()
{
return $this->headers;
}
/**
* Get raw headers.
*
* @return array
*/
public function getRawHeaders()
{
$headers = [];
foreach ($this->headers as $key => $value) {
$headers[] = $key . ': ' . $value;
}
return $headers;
}
/**
* Get body.
*
* @return string
*/
public function getBody()
{
return $this->body;
}
}
class WCOAuth
{
/**
* OAuth signature method algorithm.
*/
const HASH_ALGORITHM = 'SHA256';
/**
* API endpoint URL.
*
* @var string
*/
protected $url;
/**
* Consumer key.
*
* @var string
*/
protected $consumerKey;
/**
* Consumer secret.
*
* @var string
*/
protected $consumerSecret;
/**
* API version.
*
* @var string
*/
protected $apiVersion;
/**
* WCRequest method.
*
* @var string
*/
protected $method;
/**
* WCRequest parameters.
*
* @var array
*/
protected $parameters;
/**
* Timestamp.
*
* @var string
*/
protected $timestamp;
/**
* Initialize oAuth class.
*
* @param string $url Store URL.
* @param string $consumerKey Consumer key.
* @param string $consumerSecret Consumer Secret.
* @param string $method WCRequest method.
* @param string $apiVersion API version.
* @param array $parameters WCRequest parameters.
* @param string $timestamp Timestamp.
*/
public function __construct(
$url,
$consumerKey,
$consumerSecret,
$apiVersion,
$method,
$parameters = [],
$timestamp = ''
) {
$this->url = $url;
$this->consumerKey = $consumerKey;
$this->consumerSecret = $consumerSecret;
$this->apiVersion = $apiVersion;
$this->method = $method;
$this->parameters = $parameters;
$this->timestamp = $timestamp;
}
/**
* Encode according to RFC 3986.
*
* @param string|array $value Value to be normalized.
*
* @return string
*/
//TODO Rückgbabetyp prüfen
protected function encode($value)
{
if (is_array($value)) {
return array_map([$this, 'encode'], $value);
} else {
return str_replace(['+', '%7E'], [' ', '~'], rawurlencode($value));
}
}
/**
* Normalize parameters.
*
* @param array $parameters Parameters to normalize.
*
* @return array
*/
protected function normalizeParameters($parameters)
{
$normalized = [];
foreach ($parameters as $key => $value) {
// Percent symbols (%) must be double-encoded.
$key = $this->encode($key);
$value = $this->encode($value);
$normalized[$key] = $value;
}
return $normalized;
}
/**
* Process filters.
*
* @param array $parameters WCRequest parameters.
*
* @return array
*/
protected function processFilters($parameters)
{
if (isset($parameters['filter'])) {
$filters = $parameters['filter'];
unset($parameters['filter']);
foreach ($filters as $filter => $value) {
$parameters['filter[' . $filter . ']'] = $value;
}
}
return $parameters;
}
/**
* Get secret.
*
* @return string
*/
protected function getSecret()
{
$secret = $this->consumerSecret;
// Fix secret for v3 or later.
if (!\in_array($this->apiVersion, ['v1', 'v2'])) {
$secret .= '&';
}
return $secret;
}
/**
* Generate oAuth1.0 signature.
*
* @param array $parameters WCRequest parameters including oauth.
*
* @return string
*/
protected function generateOauthSignature($parameters)
{
$baseRequestUri = rawurlencode($this->url);
// Extract filters.
$parameters = $this->processFilters($parameters);
// Normalize parameter key/values and sort them.
$parameters = $this->normalizeParameters($parameters);
uksort($parameters, 'strcmp');
// Set query string.
$queryString = implode('%26', $this->joinWithEqualsSign($parameters)); // Join with ampersand.
$stringToSign = $this->method . '&' . $baseRequestUri . '&' . $queryString;
$secret = $this->getSecret();
return base64_encode(hash_hmac(self::HASH_ALGORITHM, $stringToSign, $secret, true));
}
/**
* Creates an array of urlencoded strings out of each array key/value pairs.
*
* @param array $params Array of parameters to convert.
* @param array $queryParams Array to extend.
* @param string $key Optional Array key to append
* @return string Array of urlencoded strings
*/
protected function joinWithEqualsSign($params, $queryParams = [], $key = '')
{
foreach ($params as $paramKey => $paramValue) {
if ($key) {
$paramKey = $key . '%5B' . $paramKey . '%5D'; // Handle multi-dimensional array.
}
if (is_array($paramValue)) {
//TODO Typ prüfen
$queryParams = $this->joinWithEqualsSign($paramValue, $queryParams, $paramKey);
} else {
$string = $paramKey . '=' . $paramValue; // Join with equals sign.
$queryParams[] = $this->encode($string);
}
}
return $queryParams;
}
/**
* Sort parameters.
*
* @param array $parameters Parameters to sort in byte-order.
*
* @return array
*/
protected function getSortedParameters($parameters)
{
uksort($parameters, 'strcmp');
foreach ($parameters as $key => $value) {
if (is_array($value)) {
uksort($parameters[$key], 'strcmp');
}
}
return $parameters;
}
/**
* Get oAuth1.0 parameters.
*
* @return string
*/
public function getParameters()
{
$parameters = \array_merge($this->parameters, [
'oauth_consumer_key' => $this->consumerKey,
'oauth_timestamp' => $this->timestamp,
'oauth_nonce' => \sha1(\microtime()),
'oauth_signature_method' => 'HMAC-' . self::HASH_ALGORITHM,
]);
// The parameters above must be included in the signature generation.
$parameters['oauth_signature'] = $this->generateOauthSignature($parameters);
//TODO Typ prüfen
return $this->getSortedParameters($parameters);
}
}
class WCHttpClientException extends \Exception
{
/**
* WCRequest.
*
* @var WCRequest
*/
private $request;
/**
* WCResponse.
*
* @var WCResponse
*/
private $response;
/**
* Initialize exception.
*
* @param string $message Error message.
* @param int $code Error code.
* @param WCRequest $request Request data.
* @param WCResponse $response Response data.
*/
public function __construct($message, $code, WCRequest $request, WCResponse $response)
{
parent::__construct($message, $code);
$this->request = $request;
$this->response = $response;
}
/**
* Get request data.
*
* @return WCRequest
*/
public function getRequest()
{
return $this->request;
}
/**
* Get response data.
*
* @return WCResponse
*/
public function getResponse()
{
return $this->response;
}
}
class WCHttpClient
{
/**
* cURL handle.
*
* @var resource
*/
protected $ch;
/**
* Store API URL.
*
* @var string
*/
protected $url;
/**
* Consumer key.
*
* @var string
*/
protected $consumerKey;
/**
* Consumer secret.
*
* @var string
*/
protected $consumerSecret;
/**
* WCClient options.
*
* @var WCOptions
*/
protected $options;
/**
* WCRequest.
*
* @var WCRequest
*/
private $request;
/**
* WCResponse.
*
* @var WCResponse
*/
private $response;
/**
* WCResponse headers.
*
* @var string
*/
private $responseHeaders;
/** @var Logger $logger */
public $logger;
/**
* Initialize HTTP client.
*
* @param string $url Store URL.
* @param string $consumerKey Consumer key.
* @param string $consumerSecret Consumer Secret.
* @param array $options WCClient options.
*
* @throws WCHttpClientException
*/
public function __construct($url, $consumerKey, $consumerSecret, $options, $logger)
{
if (!function_exists('curl_version')) {
throw new WCHttpClientException('cURL is NOT installed on this server', -1, new WCRequest(), new WCResponse());
}
$this->options = new WCOptions($options);
$this->url = $this->buildApiUrl($url);
$this->consumerKey = $consumerKey;
$this->consumerSecret = $consumerSecret;
$this->logger = $logger;
}
/**
* Check if is under SSL.
*
* @return bool
*/
protected function isSsl()
{
return strpos($this->url,'https://')===0;
}
/**
* Build API URL.
*
* @param string $url Store URL.
*
* @return string
*/
protected function buildApiUrl($url)
{
$api = $this->options->isWPAPI() ? $this->options->apiPrefix() : '/wc-api/';
return rtrim($url, '/') . $api . $this->options->getVersion() . '/';
}
/**
* Build URL.
*
* @param string $url URL.
* @param array $parameters Query string parameters.
*
* @return string
*/
protected function buildUrlQuery($url, $parameters = [])
{
if (!empty($parameters)) {
$url .= '?' . http_build_query($parameters);
}
return $url;
}
/**
* Authenticate.
*
* @param string $url WCRequest URL.
* @param string $method WCRequest method.
* @param array $parameters WCRequest parameters.
*
* @return array
*/
protected function authenticate($url, $method, $parameters = [])
{
// Setup authentication.
if ($this->isSsl()) {
$basicAuth = new WCBasicAuth(
$this->ch,
$this->consumerKey,
$this->consumerSecret,
$this->options->isQueryStringAuth(),
$parameters
);
$parameters = $basicAuth->getParameters();
} else {
$oAuth = new WCOAuth(
$url,
$this->consumerKey,
$this->consumerSecret,
$this->options->getVersion(),
$method,
$parameters,
$this->options->oauthTimestamp()
);
//TODO Typ prüfen
$parameters = $oAuth->getParameters();
}
return $parameters;
}
/**
* Setup method.
*
* @param string $method WCRequest method.
*/
protected function setupMethod($method)
{
if ('POST' === $method) {
curl_setopt($this->ch, CURLOPT_POST, true);
} elseif (in_array($method, ['PUT', 'DELETE', 'OPTIONS'])) {
curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $method);
}
}
/**
* Get request headers.
*
* @param bool $sendData If request send data or not.
*
* @return array
*/
protected function getRequestHeaders($sendData = false)
{
$headers = [
'Accept' => 'application/json',
'User-Agent' => $this->options->userAgent() . '/' . WCClient::VERSION,
];
if ($sendData) {
$headers['Content-Type'] = 'application/json;charset=utf-8';
}
return $headers;
}
/**
* Create request.
*
* @param string $endpoint WCRequest endpoint.
* @param string $method WCRequest method.
* @param array $data WCRequest data.
* @param array $parameters WCRequest parameters.
*
* @return WCRequest
*/
protected function createRequest($endpoint, $method, $data = [], $parameters = [])
{
$body = '';
$url = $this->url . $endpoint;
$hasData = !empty($data);
// Setup authentication.
$parameters = $this->authenticate($url, $method, $parameters);
// Setup method.
$this->setupMethod($method);
// Include post fields.
if ($hasData) {
$body = json_encode($data);
curl_setopt($this->ch, CURLOPT_POSTFIELDS, $body);
}
$this->request = new WCRequest(
$this->buildUrlQuery($url, $parameters),
$method,
$parameters,
$this->getRequestHeaders($hasData),
$body
);
return $this->getRequest();
}
/**
* Get response headers.
*
* @return array
*/
protected function getResponseHeaders()
{
$headers = [];
$lines = explode("\n", $this->responseHeaders);
$lines = array_filter($lines, 'trim');
foreach ($lines as $index => $line) {
// Remove HTTP/xxx params.
if (strpos($line, ': ') === false) {
continue;
}
list($key, $value) = explode(': ', $line);
$headers[$key] = isset($headers[$key]) ? $headers[$key] . ', ' . trim($value) : trim($value);
}
return $headers;
}
/**
* Create response.
*
* @return WCResponse
*/
protected function createResponse()
{
// Set response headers.
$this->responseHeaders = '';
curl_setopt($this->ch, CURLOPT_HEADERFUNCTION, function ($_, $headers) {
$this->responseHeaders .= $headers;
return strlen($headers);
});
// Get response data.
$body = curl_exec($this->ch);
$code = curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
$headers = $this->getResponseHeaders();
// Register response.
$this->response = new WCResponse($code, $headers, $body);
return $this->getResponse();
}
/**
* Set default cURL settings.
*/
protected function setDefaultCurlSettings()
{
// $verifySsl = $this->options->verifySsl();
$timeout = $this->options->getTimeout();
$followRedirects = $this->options->getFollowRedirects();
curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, $verifySsl);
if (!$verifySsl) {
curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, $verifySsl);
}
if ($followRedirects) {
curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, true);
}
curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, $timeout);
curl_setopt($this->ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($this->ch, CURLOPT_HTTPHEADER, $this->request->getRawHeaders());
curl_setopt($this->ch, CURLOPT_URL, $this->request->getUrl());
}
/**
* Look for errors in the request.
*
* @throws WCHttpClientException
*
* @param array $parsedResponse Parsed body response.
*/
protected function lookForErrors($parsedResponse)
{
// Any non-200/201/202 response code indicates an error.
if (!in_array($this->response->getCode(), ['200', '201', '202'])) {
$errors = isset($parsedResponse->errors) ? $parsedResponse->errors : $parsedResponse;
$errorMessage = '';
$errorCode = '';
if (is_array($errors)) {
$errorMessage = $errors[0]->message;
$errorCode = $errors[0]->code;
} elseif (isset($errors->message, $errors->code)) {
$errorMessage = $errors->message;
$errorCode = $errors->code;
}
$this->logger->error('WooCommerce Error',
[
'request' => $this->request,
'response' => $this->response
]
);
throw new WCHttpClientException(
sprintf('Error: %s [%s]', $errorMessage, $errorCode),
$this->response->getCode(),
$this->request,
$this->response
);
}
}
/**
* Process response.
*
* @throws WCHttpClientException
* @return array
*/
protected function processResponse()
{
$body = $this->response->getBody();
// Look for UTF-8 BOM and remove.
if (0 === strpos(bin2hex(substr($body, 0, 4)), 'efbbbf')) {
$body = substr($body, 3);
}
$parsedResponse = json_decode($body);
// Test if return a valid JSON.
if (JSON_ERROR_NONE !== json_last_error()) {
$message = function_exists('json_last_error_msg') ? json_last_error_msg() : 'Invalid JSON returned';
throw new WCHttpClientException(
sprintf('JSON ERROR: %s', $message),
$this->response->getCode(),
$this->request,
$this->response
);
}
$this->lookForErrors($parsedResponse);
return $parsedResponse;
}
/**
* Make requests.
*
* @param string $endpoint WCRequest endpoint.
* @param string $method WCRequest method.
* @param array $data WCRequest data.
* @param array $parameters WCRequest parameters.
*
* @throws WCHttpClientException
*
* @return array
*/
public function request($endpoint, $method, $data = [], $parameters = [])
{
// Initialize cURL.
$this->ch = curl_init();
// Set request args.
$request = $this->createRequest($endpoint, $method, $data, $parameters);
// Default cURL settings.
$this->setDefaultCurlSettings();
// Get response.
$response = $this->createResponse();
// Check for cURL errors.
if (curl_errno($this->ch)) {
throw new WCHttpClientException('cURL Error: ' . \curl_error($this->ch), 0, $request, $response);
}
curl_close($this->ch);
return $this->processResponse();
}
/**
* Get request data.
*
* @return WCRequest
*/
public function getRequest()
{
return $this->request;
}
/**
* Get response data.
*
* @return WCResponse
*/
public function getResponse()
{
return $this->response;
}
}
class WCBasicAuth
{
/**
* cURL handle.
*
* @var resource
*/
protected $ch;
/**
* Consumer key.
*
* @var string
*/
protected $consumerKey;
/**
* Consumer secret.
*
* @var string
*/
protected $consumerSecret;
/**
* Do query string auth.
*
* @var bool
*/
protected $doQueryString;
/**
* WCRequest parameters.
*
* @var array
*/
protected $parameters;
/**
* Initialize Basic Authentication class.
*
* @param resource $ch cURL handle.
* @param string $consumerKey Consumer key.
* @param string $consumerSecret Consumer Secret.
* @param bool $doQueryString Do or not query string auth.
* @param array $parameters WCRequest parameters.
*/
public function __construct($ch, $consumerKey, $consumerSecret, $doQueryString, $parameters = [])
{
$this->ch = $ch;
$this->consumerKey = $consumerKey;
$this->consumerSecret = $consumerSecret;
$this->doQueryString = $doQueryString;
$this->parameters = $parameters;
$this->processAuth();
}
/**
* Process auth.
*/
protected function processAuth()
{
if ($this->doQueryString) {
$this->parameters['consumer_key'] = $this->consumerKey;
$this->parameters['consumer_secret'] = $this->consumerSecret;
} else {
\curl_setopt($this->ch, CURLOPT_USERPWD, $this->consumerKey . ':' . $this->consumerSecret);
}
}
/**
* Get parameters.
*
* @return array
*/
public function getParameters()
{
return $this->parameters;
}
}