Skip to content

Instantly share code, notes, and snippets.

@andykillen
Last active May 19, 2022 07:33
Show Gist options
  • Save andykillen/e68db8f52bf3315e0a971d626509befa to your computer and use it in GitHub Desktop.
Save andykillen/e68db8f52bf3315e0a971d626509befa to your computer and use it in GitHub Desktop.
<?php
/**
* copyrights andrew killen 2017
*
* Attribution-ShareAlike 4.0 International.
**/
/**
* This will get the contents from the db about the current values, optionally if you have setup
* a custom taxonomy you can say "I want from supplier ID n" this helps as SKU's can often be the same
* for many suppliers, it will then load/download an XML file and convert it to an Array,
* and finally loop through the DB current values, looking for them in the feed array by SKU, if they exist
* then it will try to update the meta, logging ths changes made.
*
**/
class xmlUpdateWooCommerce {
// place on the system where CRON downloads the file. this is normally 10 --> 20X faster than php
protected $fileSystemFile = '/path/to/cron/downloaded/file.xml';
// fall back to remote url
protected $feedUrl = "https://somesite.com/feed.xml";
// where the database current values are held
protected $result;
// the XML that is loaded
protected $xml;
/**
* How to run the whole thing.
* @param $supplier int, optional tax term id that can be used to define all products from one supplier
*/
public function init($supplier = 0){
$this->getCurrentDbValues($supplier);
$this->openFeedAsXML();
$this->feedLoopAndUpdate()
}
/**
* Use $wpdb and a generated select statement to return an object with all
* of the needed produt meta + product ID
*
* @param $supplier int taxonomy term id
*
* @return object
*/
protected function getCurrentDbValues($supplier = 0){
global $wpdb;
// set the post type to search through
$post_type = 'product';
// postmeta data that will be joined to the result
$metas = ['_sku','_stock', '_regular_price','_price'];
// look the metas to add the sql statement to make the joins
foreach ($metas as $i => $meta_key) {
$meta_fields[] = 'm' . $i . '.meta_value as ' . $meta_key;
$meta_joins[] = ' left join ' . $wpdb->postmeta . ' as m' . $i . ' on m' . $i . '.post_id=' . $wpdb->posts . '.ID and m' . $i . '.meta_key="' . $meta_key . '"';
}
// begin the SQL statemnt adding in the meta fields to ID
$request = "SELECT ID, " . join(',', $meta_fields) . " FROM $wpdb->posts ";
// add the joins
$request .= join(' ', $meta_joins);
// make sure its using the right post status and post type
$request .= " WHERE {$wpdb->posts}.post_status='publish' AND {$wpdb->posts}.post_type='{$post_type}'";
/**
* Optional, use supplier taxonomy to reduce the total number of records
* returned. This is often needed as SKU's can be the same from many suppliers.
*
* It should really double check to see if the term tax id exists first...
**/
if($supplier != 0){
$request .= " AND ID IN(
select object_id FROM {$wpdb->prefix}term_relationships where term_taxonomy_id ='{$supplier}'
)";
}
$this->result = $wpdb->get_results($request);
}
/**
* tries to get CRON downloaded file first
*
* Idealy this should check the file datetime to see if it out of date
*
* @return void
**/
protected function openFeedAsXML(){
if(file_exists($this->fileSystemFile)){
$this->xml = simplexml_load_file($this->fileSystemFile, NULL, LIBXML_NOCDATA);
} else {
$content = file_get_contents($this->feedUrl);
$this->xml = simplexml_load_string($content, NULL, LIBXML_NOCDATA);
}
}
/**
* Loop through the feed XML converting it to an assoicative array
* where the sku is the key, and the other values are in an array assoicated
* with it.
*
* this will convert the entire feed, not just the items that are in the db.
* becuase of the use late of isset(), it will make it uber fast.
*
* If I was making this for anyone else I would have spilt this up a bit into separate methods
* and not nested loops, but I love doing it this way. nested loops make me happy :)
*/
protected function feedLoopAndUpdate(){
// VidaXL use /products/product to hold the stock items. change this as needed
$nodes = $this->xml->xpath("/products/product");
$output = [];
// This is part of the magic here. make sure the SKU is the key
foreach($nodes as $node ){
$output [(int) $node->SKU] = [
'stock' => (int) $node->Stock,
'rrp' => (string) $node->RRP,
];
}
// loop through the result
foreach( $this->result as $row ){
$check = [
'_stock' => 'stock',
'_price' => 'rrp' ,
'_regular_price' => 'rrp',
];
// check the feed array if the sku exists. if it does work on it
if(isset($output[$row->_sku])){
// loop through the fiels/post meta you can to check
foreach($check as $meta => $field) {
// continue to next meta is this meta does not exist
if(empty($row->{$meta})){
continue;
}
// If the current meta value is different to the one in the feed, update.
if($output[$row->_sku][$field] != $row->{$meta}){
/**
* some error logging so you can see what happend
*/
error_log( "ID: {$row->ID} SKU: {$row->_sku} - changing $meta to be: {$output[$row->_sku][$field]} , $meta was: {$row->{$meta}}" );
if(is_admin()){
echo "ID: {$row->ID} SKU: {$row->_sku} - changing $meta to be: {$output[$row->_sku][$field]} , $meta was: {$row->{$meta}} ";
echo "<a href='".get_permalink($row->ID)."'>". get_the_title($row->ID) ."</a> <br/>";
}
/*
* update the post meta for that value
*/
update_post_meta($row->ID, $meta, $output[$row->_sku][$field]);
}
}
} else {
$this->whatToDoIfSKUdoesNotExistInFeed($row->ID);
}
}
}
protected function whatToDoIfSKUdoesNotExistInFeed($id) {
// do something like send an email, or create a report that tells
// the system owner that the product is no longer available in the feed
// this will stop orphaned products, or products that say they have stock
// when the supplier cannot deliver.
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment