diff --git a/src/include/class-wc-retailcrm-icml.php b/src/include/class-wc-retailcrm-icml.php index 03b8cc5..ffd3a3e 100644 --- a/src/include/class-wc-retailcrm-icml.php +++ b/src/include/class-wc-retailcrm-icml.php @@ -1,25 +1,20 @@ - * @license http://retailcrm.ru Proprietary - * @link http://retailcrm.ru - * @see http://help.retailcrm.ru - */ - if (!class_exists('WC_Retailcrm_Icml')) : + /** + * PHP version 7.0 + * + * Class WC_Retailcrm_Icml - Generate ICML file (catalog). + * + * @category Integration + * @author RetailCRM + * @license http://retailcrm.ru Proprietary + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ class WC_Retailcrm_Icml { - protected $shop; - protected $file; - protected $tmpFile; - - protected $properties = [ + const OFFER_PROPERTIES = [ 'name', 'productName', 'price', @@ -31,19 +26,11 @@ if (!class_exists('WC_Retailcrm_Icml')) : 'productActivity' ]; - protected $xml; - - /** @var SimpleXMLElement $categories */ - protected $categories; - - /** @var SimpleXMLElement $categories */ - protected $offers; - - protected $chunk = 500; - protected $fileLifeTime = 3600; - - /** @var array */ + protected $shop; + protected $file; + protected $tmpFile; protected $settings; + protected $icmlWriter; /** * WC_Retailcrm_Icml constructor. @@ -51,10 +38,11 @@ if (!class_exists('WC_Retailcrm_Icml')) : */ public function __construct() { - $this->settings = get_option(WC_Retailcrm_Base::$option_key); - $this->shop = get_bloginfo('name'); - $this->file = ABSPATH . 'simla.xml'; - $this->tmpFile = sprintf('%s.tmp', $this->file); + $this->shop = get_bloginfo('name'); + $this->file = ABSPATH . 'simla.xml'; + $this->tmpFile = sprintf('%s.tmp', $this->file); + $this->settings = get_option(WC_Retailcrm_Base::$option_key); + $this->icmlWriter = new WC_Retailcrm_Icml_Writer($this->tmpFile); } /** @@ -65,282 +53,23 @@ if (!class_exists('WC_Retailcrm_Icml')) : $start = microtime(true); $memory = memory_get_usage(); + $this->icmlWriter->writeHead($this->shop); + $categories = $this->prepareCategories(); - if (file_exists($this->tmpFile)) { - if (filectime($this->tmpFile) + $this->fileLifeTime < time()) { - unlink($this->tmpFile); - $this->writeHead(); - } - } else { - $this->writeHead(); + if (empty($categories)) { + writeBaseLogs('Can`t get categories!'); + return; } - try { - if (!empty($categories)) { - $this->writeCategories($categories); - unset($categories); - } + $this->icmlWriter->writeCategories($categories); - $this->writeProducts(); + $this->writeProducts(); - $dom = dom_import_simplexml(simplexml_load_file($this->tmpFile))->ownerDocument; + $this->icmlWriter->writeEnd(); + $this->icmlWriter->formatXml($this->tmpFile); - $dom->formatOutput = true; - - $formatted = $dom->saveXML(); - - unset($dom, $this->xml); - - file_put_contents($this->tmpFile, $formatted); - rename($this->tmpFile, $this->file); - } catch (Exception $e) { - unlink($this->tmpFile); - } - - WC_Retailcrm_Logger::debug(__METHOD__, ['Count products: ', $this->getCountProducts()]); - WC_Retailcrm_Logger::debug(__METHOD__, ['Time test:', 'Time: ' . round(microtime(true) - $start, 2) . ' seconds']); - WC_Retailcrm_Logger::debug(__METHOD__, ['Memory test:', 'Memory: ' . (memory_get_usage() - $memory) . ' byte']); - } - - /** - * Load tmp data - * - * @return \SimpleXMLElement - */ - private function loadXml() - { - return new SimpleXMLElement( - $this->tmpFile, - LIBXML_NOENT | LIBXML_NOCDATA | LIBXML_COMPACT | LIBXML_PARSEHUGE, - true - ); - } - - /** - * Generate xml header - */ - private function writeHead() - { - $string = sprintf( - '%s', - current_time('Y-m-d H:i:s'), - html_entity_decode($this->shop) - ); - - file_put_contents($this->tmpFile, $string, LOCK_EX); - } - - /** - * Write categories in file - * - * @param $categories - */ - private function writeCategories($categories) - { - $chunkCategories = array_chunk($categories, $this->chunk); - - foreach ($chunkCategories as $categories) { - $this->xml = $this->loadXml(); - - $this->categories = $this->xml->shop->categories; - $this->addCategories($categories); - - $this->xml->asXML($this->tmpFile); - } - - unset($this->categories); - } - - /** - * Write products in file - * - * @param $offer - */ - private function writeOffer($offer) - { - $this->xml = $this->loadXml(); - - $this->offers = $this->xml->shop->offers; - $this->addOffer($offer); - - $this->xml->asXML($this->tmpFile); - } - - /** - * Add categories - * - * @param $categories - */ - private function addCategories($categories) - { - $categories = self::filterRecursive($categories); - - foreach ($categories as $category) { - if (!array_key_exists('name', $category) || !array_key_exists('id', $category)) { - continue; - } - - /** @var SimpleXMLElement $e */ - /** @var SimpleXMLElement $cat */ - - $cat = $this->categories; - $e = $cat->addChild('category'); - - $e->addAttribute('id', $category['id']); - - if (array_key_exists('parentId', $category) && $category['parentId'] > 0) { - $e->addAttribute('parentId', $category['parentId']); - } - - $e->addChild('name', $category['name']); - - if (array_key_exists('picture', $category)) { - $e->addChild('picture', $category['picture']); - } - } - } - - /** - * Add offers - * - * @param $offers - */ - private function addOffer($offers) - { - //$offers = self::filterRecursive($offers); - - foreach ($offers as $key => $offer) { - if (!array_key_exists('id', $offer)) { - continue; - } - - $e = $this->offers->addChild('offer'); - - $e->addAttribute('id', $offer['id']); - - if (!array_key_exists('productId', $offer) || empty($offer['productId'])) { - $offer['productId'] = $offer['id']; - } - $e->addAttribute('productId', $offer['productId']); - - if (!empty($offer['quantity'])) { - $e->addAttribute('quantity', (int) $offer['quantity']); - } else { - $e->addAttribute('quantity', 0); - } - - if (isset($offer['categoryId']) && $offer['categoryId']) { - if (is_array($offer['categoryId'])) { - foreach ($offer['categoryId'] as $categoryId) { - $e->addChild('categoryId', $categoryId); - } - } else { - $e->addChild('categoryId', $offer['categoryId']); - } - } - - if (!array_key_exists('name', $offer) || empty($offer['name'])) { - $offer['name'] = 'Без названия'; - } - - if (!array_key_exists('productName', $offer) || empty($offer['productName'])) { - $offer['productName'] = $offer['name']; - } - - if (array_key_exists('picture', $offer) && !empty($offer['picture'])) { - foreach ($offer['picture'] as $urlImage) { - $e->addChild('picture', $urlImage); - } - } - - unset($offer['id'], $offer['productId'], $offer['categoryId'], $offer['quantity'], $offer['picture']); - array_walk($offer, [$this, 'setOffersProperties'], $e); - - if (array_key_exists('params', $offer) && !empty($offer['params'])) { - array_walk($offer['params'], [$this, 'setOffersParams'], $e); - } - - if (array_key_exists('dimensions', $offer)) { - $e->addChild('dimensions', $offer['dimensions']); - } - - if (array_key_exists('weight', $offer)) { - $e->addChild('weight', $offer['weight']); - } - - if (array_key_exists('tax', $offer)) { - $e->addChild('vatRate', $offer['tax']); - } - } - } - - /** - * Set offer properties - * - * @param $value - * @param $key - * @param $e - */ - private function setOffersProperties($value, $key, &$e) - { - if (in_array($key, $this->properties) && $key != 'params') { - /** @var SimpleXMLElement $e */ - $e->addChild($key, htmlspecialchars($value)); - } - } - - /** - * Set offer params - * - * @param $value - * @param $key - * @param $e - */ - private function setOffersParams($value, $key, &$e) - { - if ( - array_key_exists('code', $value) && - array_key_exists('name', $value) && - array_key_exists('value', $value) && - !empty($value['code']) && - !empty($value['name']) && - !empty($value['value']) - ) { - /** @var SimpleXMLElement $e */ - $param = $e->addChild('param', htmlspecialchars($value['value'])); - $param->addAttribute('code', $value['code']); - $param->addAttribute('name', substr(htmlspecialchars($value['name']), 0, 200)); - unset($key); - } - } - - /** - * Filter result array - * - * @param $haystack - * - * @return mixed - */ - public static function filterRecursive($haystack) - { - foreach ($haystack as $key => $value) { - if (is_array($value)) { - $haystack[$key] = self::filterRecursive($haystack[$key]); - } - - if ( - is_null($haystack[$key]) - || $haystack[$key] === '' - || (is_array($haystack[$key]) && count($haystack[$key]) == 0) - ) { - unset($haystack[$key]); - } elseif (!is_array($value)) { - $haystack[$key] = trim($value); - } - } - - return $haystack; + rename($this->tmpFile, $this->file); } /** @@ -369,7 +98,7 @@ if (!class_exists('WC_Retailcrm_Icml')) : do { $products = wc_get_products( [ - 'limit' => 2000, + 'limit' => 1000, 'status' => $statusArgs, 'page' => $page, 'paginate' => true, @@ -378,11 +107,13 @@ if (!class_exists('WC_Retailcrm_Icml')) : if (empty($products)) { writeBaseLogs('Can`t get products!'); + return; } - $offer = $this->prepareOffer($products->products, $productAttributes); - $this->writeOffer($offer); + $offer = $this->prepareOffers($products->products, $productAttributes); + + $this->icmlWriter->writeOffers($offer); $page++; } while ($page <= $products->max_num_pages); @@ -394,7 +125,7 @@ if (!class_exists('WC_Retailcrm_Icml')) : * * @return Generator */ - private function prepareOffer($products, $productAttributes) + private function prepareOffers($products, $productAttributes) { foreach ($products as $offer) { $type = $offer->get_type(); @@ -415,56 +146,6 @@ if (!class_exists('WC_Retailcrm_Icml')) : } } - /** - * Get WC categories - * - * @return array - */ - private function prepareCategories() - { - $categories = []; - $taxonomy = 'product_cat'; - $orderby = 'parent'; - $show_count = 0; // 1 for yes, 0 for no - $pad_counts = 0; // 1 for yes, 0 for no - $hierarchical = 1; // 1 for yes, 0 for no - $title = ''; - $empty = 0; - - $args = [ - 'taxonomy' => $taxonomy, - 'orderby' => $orderby, - 'show_count' => $show_count, - 'pad_counts' => $pad_counts, - 'hierarchical' => $hierarchical, - 'title_li' => $title, - 'hide_empty' => $empty - ]; - - $wcatTerms = get_categories($args); - - foreach ($wcatTerms as $term) { - $category = [ - 'id' => $term->term_id, - 'parentId' => $term->parent, - 'name' => $term->name - ]; - - $thumbnail_id = function_exists('get_term_meta') - ? get_term_meta($term->term_id, 'thumbnail_id', true) - : get_woocommerce_term_meta($term->term_id, 'thumbnail_id', true); - $picture = wp_get_attachment_url($thumbnail_id); - - if ($picture) { - $category['picture'] = $picture; - } - - $categories[] = $category; - } - - return $categories; - } - /** * Get offer for ICML catalog * @@ -628,6 +309,57 @@ if (!class_exists('WC_Retailcrm_Icml')) : : $product->get_short_description(); } + /** + * Get WC categories + * + * @return array + */ + private function prepareCategories() + { + $categories = []; + $taxonomy = 'product_cat'; + $orderby = 'parent'; + $show_count = 0; // 1 for yes, 0 for no + $pad_counts = 0; // 1 for yes, 0 for no + $hierarchical = 1; // 1 for yes, 0 for no + $title = ''; + $empty = 0; + + $args = [ + 'taxonomy' => $taxonomy, + 'orderby' => $orderby, + 'show_count' => $show_count, + 'pad_counts' => $pad_counts, + 'hierarchical' => $hierarchical, + 'title_li' => $title, + 'hide_empty' => $empty + ]; + + $wcTerms = get_categories($args); + + foreach ($wcTerms as $term) { + $category = [ + 'id' => $term->term_id, + 'parentId' => $term->parent, + 'name' => $term->name + ]; + + $thumbnailId = function_exists('get_term_meta') + ? get_term_meta($term->term_id, 'thumbnail_id', true) + : get_woocommerce_term_meta($term->term_id, 'thumbnail_id', true); + + $picture = wp_get_attachment_url($thumbnailId); + + if ($picture) { + $category['picture'] = $picture; + } + + $categories[] = $category; + } + + return $categories; + } + private function getCountProducts() { global $wpdb; diff --git a/src/include/icml/class-wc-retailcrm-icml-writer.php b/src/include/icml/class-wc-retailcrm-icml-writer.php new file mode 100644 index 0000000..e41a209 --- /dev/null +++ b/src/include/icml/class-wc-retailcrm-icml-writer.php @@ -0,0 +1,255 @@ + + * @license http://retailcrm.ru Proprietary + * @link http://retailcrm.ru + * @see http://help.retailcrm.ru + */ + class WC_Retailcrm_Icml_Writer + { + private $writer; + + public function __construct($tmpFile) + { + $this->writer = new \XMLWriter(); + $this->writer->openUri($tmpFile); + } + + /** + * Write HEAD in ICML catalog. + * + * @param string $shop + * + * @return void + */ + public function writeHead(string $shop) + { + $this->writer->startDocument('1.0', 'UTF-8'); + $this->writer->startElement('yml_catalog'); // start + $this->writer->writeAttribute('date', date('Y-m-d H:i:s')); + $this->writer->startElement('shop'); // start + $this->writer->WriteElement('name', $shop); + } + + /** + * Write categories in ICML catalog. + * + * @param array $categories + * + * @return void + */ + public function writeCategories(array $categories) + { + $this->writer->startElement('categories'); // start + + $this->addCategories($categories); + + $this->writer->endElement(); // end + } + + /** + * Add category in ICML catalog. + * + * @param array $categories + * + * @return void + */ + private function addCategories(array $categories) + { + foreach ($categories as $category) { + if (!array_key_exists('name', $category) || !array_key_exists('id', $category)) { + continue; + } + + $this->writer->startElement('category'); // start + + $this->writer->writeAttribute('id', $category['id']); + + if (array_key_exists('parentId', $category) && 0 < $category['parentId']) { + $this->writer->writeAttribute('parentId', $category['parentId']); + } + + $this->writer->writeElement('name', $category['name']); + + if (array_key_exists('picture', $category) && $category['picture']) { + $this->writer->writeElement('picture', $category['picture']); + } + + $this->writer->endElement(); // end + } + } + + /** + * Write offers in ICML catalog. + * + * @return void + */ + public function writeOffers($offers) + { + $this->writer->startElement('offers'); // start + + $this->addOffers($offers); + + $this->writer->endElement(); // end + } + + /** + * Add offer in ICML catalog. + * + * @return void + */ + private function addOffers($offers) + { + foreach ($offers as $offer) { + if (!array_key_exists('id', $offer)) { + continue; + } + + $this->writer->startElement('offer'); // start + + if (!array_key_exists('productId', $offer) || empty($offer['productId'])) { + $offer['productId'] = $offer['id']; + } + + $this->writer->writeAttribute('id', $offer['id']); + $this->writer->writeAttribute('productId', $offer['productId']); + $this->writer->writeAttribute('quantity', (int) $offer['quantity'] ?? 0); + + if (isset($offer['categoryId'])) { + if (is_array($offer['categoryId'])) { + foreach ($offer['categoryId'] as $categoryId) { + $this->writer->writeElement('categoryId', $categoryId); + } + } else { + $this->writer->writeElement('categoryId', $offer['$categoryId']); + } + } + + if (!empty($offer['picture'])) { + foreach ($offer['picture'] as $urlImage) { + $this->writer->writeElement('picture', $urlImage); + } + } + + if (empty($offer['name'])) { + $offer['name'] = __('Untitled', 'retailcrm'); + } + + if (empty($offer['productName'])) { + $offer['productName'] = $offer['name']; + } + + unset($offer['id'], $offer['productId'], $offer['categoryId'], $offer['quantity'], $offer['picture']); + + $this->writeOffersProperties($offer); + + if (!empty($offer['params'])) { + $this->writeOffersParams($offer['params']); + } + + if (!empty($offer['dimensions'])) { + $this->writer->writeElement('dimensions', $offer['dimensions']); + } + + if (!empty($offer['weight'])) { + $this->writer->writeElement('weight', $offer['weight']); + } + + if (!empty($offer['tax'])) { + $this->writer->writeElement('vatRate', $offer['tax']); + } + + $this->writer->endElement(); // end + } + } + + /** + * Set offer properties. + * + * @param array $offerProperties + * + * @return void + */ + private function writeOffersProperties(array $offerProperties) + { + foreach ($offerProperties as $key => $value) { + if (!in_array($key, WC_Retailcrm_Icml::OFFER_PROPERTIES)) { + continue; + } + + if (is_array($value)) { + foreach ($value as $element) { + $this->writer->writeElement($key, $element); + } + } else { + $this->writer->writeElement($key, $value); + } + } + } + + /** + * Set offer params. + * + * @param array $offerParams + * + * @return void + */ + private function writeOffersParams(array $offerParams) + { + WC_Retailcrm_Logger::debug(__METHOD__, ['Hello', $offerParams['params']]); + + foreach ($offerParams as $param) { + if ( + empty($param['code']) + || empty($param['name']) + || empty($param['value']) + ) { + continue; + } + + $this->writer->startElement('param'); // start + + $this->writer->writeAttribute('code', $param['code']); + $this->writer->writeAttribute('name', $param['name']); + $this->writer->text($param['value']); + + $this->writer->endElement(); // end + } + } + + /** + * Write end tags in ICML catalog. + * + * @return void + */ + public function writeEnd() + { + $this->writer->endElement(); // end + $this->writer->endElement(); // end + $this->writer->endDocument(); + } + + /** + * Save ICML catalog. + * + * @return void + */ + public function formatXml($tmpfile) + { + $dom = dom_import_simplexml(simplexml_load_file($tmpfile))->ownerDocument; + $dom->formatOutput = true; + $formatted = $dom->saveXML(); + + unset($dom, $this->writer); + + file_put_contents($tmpfile, $formatted); + } + } +endif;