diff --git a/README.md b/README.md
index 07d0f0c..ce2a862 100644
--- a/README.md
+++ b/README.md
@@ -5,11 +5,11 @@
**Requires at least:** 5.1
**Tested up to:** 6.6.1
**Requires PHP:** 7.0
-**Stable tag:** 0.1.1
+**Stable tag:** 0.2.0
**License:** GPLv2 or later
**License URI:** https://www.gnu.org/licenses/gpl-2.0.html
-A beautiful way to display products from affiliate network product feeds on your website. Currently supports TradeTracker and AdTraction.
+A beautiful way to display products from affiliate network product feeds on your website. Currently supports Daisycon, TradeTracker and AdTraction.
## Description ##
@@ -19,6 +19,7 @@ products from product feeds of various affiliate networks.
Networks currently supported:
* AdTraction
* TradeTracker
+* Daisycon
**Features:**
@@ -83,6 +84,12 @@ Colors can be adjusted by overriding the default CSS variables:
## Changelog ##
+### 0.2.0 ###
+* Added: Uninstall function
+* Added: Daisycon support
+* Added: Shortcode for direct links to products
+* Improved: SQL logic
+
### 0.1.1 ###
* Added: Product ID and Feed in selection section
* Added: AdTraction sale price
diff --git a/affiliate-product-highlights.php b/affiliate-product-highlights.php
index 610be6c..5f2ef7b 100644
--- a/affiliate-product-highlights.php
+++ b/affiliate-product-highlights.php
@@ -7,7 +7,7 @@
* Author URI: https://koenreus.com
* Text Domain: affiliate-product-highlights
* Domain Path: /languages
- * Version: 0.1.1
+ * Version: 0.2.0
*
* @package Affiliate_Product_Highlights
*/
@@ -18,6 +18,7 @@
register_activation_hook(__FILE__, ['Koen12344\AffiliateProductHighlights\Plugin', 'activate']);
register_deactivation_hook(__FILE__, ['Koen12344\AffiliateProductHighlights\Plugin', 'deactivate']);
+register_uninstall_hook(__FILE__, ['Koen12344\AffiliateProductHighlights\Plugin', 'uninstall']);
$affiliate_product_highlights = new Plugin(__FILE__);
diff --git a/readme.txt b/readme.txt
index 8de6e21..0a2d64b 100644
--- a/readme.txt
+++ b/readme.txt
@@ -5,11 +5,11 @@ Tags: tradetracker, adtraction, affiliate, feed, products
Requires at least: 5.1
Tested up to: 6.6.1
Requires PHP: 7.0
-Stable tag: 0.1.1
+Stable tag: 0.2.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
-A beautiful way to display products from affiliate network product feeds on your website. Currently supports TradeTracker and AdTraction.
+A beautiful way to display products from affiliate network product feeds on your website. Currently supports Daisycon, TradeTracker and AdTraction.
== Description ==
@@ -19,6 +19,7 @@ products from product feeds of various affiliate networks.
Networks currently supported:
* AdTraction
* TradeTracker
+* Daisycon
**Features:**
@@ -79,6 +80,12 @@ Colors can be adjusted by overriding the default CSS variables:
== Changelog ==
+= 0.2.0 =
+* Added: Uninstall function
+* Added: Daisycon support
+* Added: Shortcode for direct links to products
+* Improved: SQL logic
+
= 0.1.1 =
* Added: Product ID and Feed in selection section
* Added: AdTraction sale price
diff --git a/src/php/BackgroundProcessing/BackgroundProcess.php b/src/php/BackgroundProcessing/BackgroundProcess.php
index fb0f2b2..f13ca01 100644
--- a/src/php/BackgroundProcessing/BackgroundProcess.php
+++ b/src/php/BackgroundProcessing/BackgroundProcess.php
@@ -2,7 +2,11 @@
namespace Koen12344\AffiliateProductHighlights\BackgroundProcessing;
-use Koen12344\AffiliateProductHighlights\Provider\AdTraction\ProductMapping;
+use Exception;
+use InvalidArgumentException;
+use Koen12344\AffiliateProductHighlights\Provider\AdTraction\ProductMapping as AdtractionProductMapping;
+use Koen12344\AffiliateProductHighlights\Provider\Daisycon\ProductMapping as DaisyconProductMapping;
+use Koen12344\AffiliateProductHighlights\Provider\TradeTracker\ProductMapping as TradeTrackerProductMapping;
use SimpleXMLElement;
use WP_Http;
use XMLReader;
@@ -55,7 +59,7 @@ public function import_images(int $feed_id, int $product_id, $images){
public function download_xml_file($url) {
if (filter_var($url, FILTER_VALIDATE_URL) === false) {
- return false;
+ throw new InvalidArgumentException(__('Invalid feed URL', 'affiliate-product-highlights'));
}
if(!function_exists('wp_tempnam')){
@@ -66,7 +70,7 @@ public function download_xml_file($url) {
$handle = fopen($tmp_file, 'w');
if ($handle === false) {
- return false;
+ throw new Exception(__('Unable to to get write access to store temporary file.', 'affiliate-product-highlights'));
}
$http = new WP_Http();
@@ -79,7 +83,7 @@ public function download_xml_file($url) {
if (is_wp_error($response)) {
@fclose($handle);
@unlink($tmp_file);
- return false;
+ throw new Exception(sprintf(__('Unable to download the feed: %s', 'affiliate-product-highlights'), $response->get_error_message()));
}
fclose($handle);
@@ -93,7 +97,7 @@ public function import_xml($file, int $feed_id, $feed_type) {
$reader->open($file);
while ($reader->read()) {
- if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == 'product') {
+ if ($reader->nodeType == XMLReader::ELEMENT && ($reader->name === 'product' || $reader->name === 'product_info')) {
$product = new SimpleXMLElement($reader->readOuterXML());
if($feed_type == 'tradetracker'){
@@ -103,14 +107,21 @@ public function import_xml($file, int $feed_id, $feed_type) {
(int)$product->campaignID,
$feed_id
));
- $mapped_product = new \Koen12344\AffiliateProductHighlights\Provider\TradeTracker\ProductMapping($product);
+ $mapped_product = new TradeTrackerProductMapping($product);
}elseif($feed_type == 'adtraction'){
$existing_product = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM " . $wpdb->prefix . 'phft_products' . " WHERE sku = %s AND feed_id = %d",
(string)$product->SKU,
$feed_id
));
- $mapped_product = new ProductMapping($product);
+ $mapped_product = new AdtractionProductMapping($product);
+ }elseif($feed_type == 'daisycon'){
+ $existing_product = $wpdb->get_row($wpdb->prepare(
+ "SELECT * FROM " . $wpdb->prefix . 'phft_products' . " WHERE sku = %s AND feed_id = %d",
+ (string)$product->sku,
+ $feed_id
+ ));
+ $mapped_product = new DaisyconProductMapping($product);
}else{
break;
}
@@ -132,29 +143,30 @@ public function import_xml($file, int $feed_id, $feed_type) {
} else {
$product_slug = sanitize_title($product_data['product_name']);
$product_data['slug'] = $product_slug;
- $inserted = false;
$suffix = 1;
- while(!$inserted){
+ while($suffix <= 20){ //Try 20 times then bail
$result = @$wpdb->insert($wpdb->prefix . 'phft_products', $product_data);
- if($result){
- $inserted = true;
- }else{
- if($wpdb->last_error && strpos($wpdb->last_error, 'Duplicate entry') !== false){
- $product_data['slug'] = $product_slug . '-' . $suffix;
- $suffix++;
- }else{
- break;
- }
+ if($result) {
+ break;
+ }
+
+ if($wpdb->last_error && strpos($wpdb->last_error, "for key 'slug_unique'") !== false){
+ $product_data['slug'] = $product_slug . '-' . $suffix;
+ $suffix++;
+ continue;
}
+ break;
}
$inserted_id = $wpdb->insert_id;
}
+ if($inserted_id > 0){
+ $this->import_images($feed_id, (int)$inserted_id, $mapped_product->get_product_images());
+ }
- $this->import_images($feed_id, (int)$inserted_id, $mapped_product->get_product_images());
}
}
@@ -174,7 +186,7 @@ private function split_feed($item){
$output_xml = new SimpleXMLElement('');
while($reader->read()){
- if ($reader->nodeType == XMLReader::ELEMENT && $reader->name === 'product') {
+ if ($reader->nodeType == XMLReader::ELEMENT && ($reader->name === 'product' || $reader->name === 'product_info')) {
$product = new SimpleXMLElement($reader->readOuterXML());
$node = dom_import_simplexml($output_xml->addChild('product'));
$node->parentNode->replaceChild($node->ownerDocument->importNode(dom_import_simplexml($product), true), $node);
@@ -223,14 +235,25 @@ private function download_feed($item){
$xml_url = get_post_meta($item['feed_id'], '_phft_feed_url', true);
- $temp_file = $this->download_xml_file($xml_url);
+ $network = $this->get_affiliate_network($xml_url);
+ if(!$network){
+ update_post_meta($item['feed_id'], '_phft_last_error', __('The affiliate network this feed belongs to is unrecognized'));
+ return false;
+ }
- update_post_meta($item['feed_id'], '_phft_last_import', time());
+ try{
+ $temp_file = $this->download_xml_file($xml_url);
+ }catch (Exception $e){
+ update_post_meta($item['feed_id'], '_phft_last_error', $e->getMessage());
+ return false;
+ }
$item['action'] = 'split_feed';
- $item['feed_type'] = $this->get_affiliate_network($xml_url);
+ $item['feed_type'] = $network;
$item['temp_file'] = $temp_file;
+ update_post_meta($item['feed_id'], '_phft_last_import', time());
+
return $item;
}
@@ -249,8 +272,9 @@ private function get_affiliate_network($url) {
$networks = [
- 'adtraction' => 'adtraction.com',
- 'tradetracker' => 'tradetracker.net',
+ 'adtraction' => 'adtraction.com',
+ 'tradetracker' => 'tradetracker.net',
+ 'daisycon' => 'daisycon.io',
];
@@ -260,6 +284,6 @@ private function get_affiliate_network($url) {
}
}
- return 'unknown';
+ return false;
}
}
diff --git a/src/php/Metabox/FeedMetabox.php b/src/php/Metabox/FeedMetabox.php
index 82fba06..4fe7bc7 100644
--- a/src/php/Metabox/FeedMetabox.php
+++ b/src/php/Metabox/FeedMetabox.php
@@ -17,10 +17,16 @@ public function get_title(): string {
}
public function render(WP_Post $post) {
+
+ $last_error = get_post_meta($post->ID, '_phft_last_error', true);
$feed_url = get_post_meta($post->ID, '_phft_feed_url', true);
$value = $feed_url ?: '';
+ if(!empty($last_error)){
+ echo sprintf(__('Last error: %s', 'affiliate-product-highlights'), $last_error);
+ }
+
echo "Feed Url: ";
}
}
diff --git a/src/php/Plugin.php b/src/php/Plugin.php
index 65fc434..2ad02dd 100644
--- a/src/php/Plugin.php
+++ b/src/php/Plugin.php
@@ -15,7 +15,7 @@ class Plugin {
const DOMAIN = 'affiliate-product-highlights';
- const VERSION = '0.1.1';
+ const VERSION = '0.2.0';
const REST_NAMESPACE = 'phft/v1';
@@ -57,6 +57,8 @@ public function __construct($file){
add_shortcode('product-highlights', [$this, 'display_products_shortcode']);
+ add_shortcode('phft-link', [$this, 'product_link_shortcode']);
+
add_action('init', function(){
add_rewrite_rule('^phft/([^/]+)/?$', 'index.php?phft_product=$matches[1]', 'top');
add_rewrite_tag('%phft_product%', '([^/]+)');
@@ -80,14 +82,14 @@ public static function activate(){
global $wpdb;
- // Set the table name
$products_table = $wpdb->prefix . 'phft_products';
- // Set the character set and collation for the table
+ $images_table = $wpdb->prefix.'phft_images';
+
$charset_collate = $wpdb->get_charset_collate();
// Define the table schema
- $products_sql = "CREATE TABLE $products_table (
+ $sql = "CREATE TABLE $products_table (
id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
feed_id bigint(20) UNSIGNED NOT NULL,
campaign_id bigint(20) UNSIGNED DEFAULT NULL,
@@ -109,12 +111,7 @@ public static function activate(){
KEY product_name_idx (product_name)
) $charset_collate;";
- // Create or update the table using dbDelta
- dbDelta($products_sql);
-
- $images_table = $wpdb->prefix.'phft_images';
-
- $images_sql = "CREATE TABLE $images_table (
+ $sql .= "CREATE TABLE $images_table (
id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
feed_id bigint(20) UNSIGNED NOT NULL,
product_id bigint(20) UNSIGNED NOT NULL,
@@ -122,10 +119,11 @@ public static function activate(){
wp_media_id bigint(20) UNSIGNED NOT NULL,
imported_at datetime NOT NULL,
PRIMARY KEY (id),
+ FOREIGN KEY (product_id) REFERENCES $products_table(id) ON DELETE CASCADE,
KEY image_url_idx (image_url)
) $charset_collate;";
- dbDelta($images_sql);
+ dbDelta($sql);
if(!wp_next_scheduled('phft_update_feeds')){
wp_schedule_event(time(), 'daily', 'phft_update_feeds');
@@ -138,6 +136,20 @@ public static function deactivate(){
}
+ public static function uninstall(){
+ global $wpdb;
+
+ //Delete all sideloaded media
+ $wp_media = $wpdb->get_results("SELECT wp_media_id FROM {$wpdb->prefix}phft_images WHERE wp_media_id > 0");
+ if($wp_media){
+ foreach($wp_media as $media){
+ wp_delete_attachment($media->wp_media_id, true);
+ }
+ }
+
+ $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}phft_images,{$wpdb->prefix}phft_products");
+ }
+
public function is_loaded(): bool {
return $this->loaded;
}
@@ -300,7 +312,7 @@ public function display_products_shortcode($atts){
public function draw_product($product){
$locale = get_locale();
$fmt = numfmt_create( $locale, NumberFormatter::CURRENCY );
- $product_url = esc_url(home_url('/phft/' . urlencode($product->slug)));
+ $product_url = esc_url(trailingslashit(home_url('/phft/' . urlencode($product->slug))));
$has_sale = $product->product_original_price > $product->product_price;
@@ -320,6 +332,26 @@ public function draw_product($product){
}
+ public function product_link_shortcode($atts, $content){
+ global $wpdb;
+
+ $atts = shortcode_atts([
+ 'product_id' => null,
+ ], $atts, 'phft-link');
+
+ if($atts['product_id'] === null){
+ return $content;
+ }
+
+ $product_id = $atts['product_id'];
+
+ $product = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$wpdb->prefix}phft_products WHERE id = %d", $product_id));
+
+ $product_url = esc_url(trailingslashit(home_url('/phft/' . urlencode($product->slug))));
+
+ return ''.$content.'';
+ }
+
public function save_feed_metabox($post_id, $post, $update){
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
@@ -346,8 +378,6 @@ public function delete_feed($post_id, \WP_Post $post){
global $wpdb;
- $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->prefix}phft_products WHERE feed_id = %d", $post_id));
-
$wp_media = $wpdb->get_results($wpdb->prepare("SELECT wp_media_id FROM {$wpdb->prefix}phft_images WHERE feed_id = %d AND wp_media_id > 0", $post_id));
if($wp_media){
@@ -356,8 +386,7 @@ public function delete_feed($post_id, \WP_Post $post){
}
}
-
- $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->prefix}phft_images WHERE feed_id = %d", $post_id));
+ $wpdb->query($wpdb->prepare("DELETE FROM {$wpdb->prefix}phft_products WHERE feed_id = %d", $post_id));
}
diff --git a/src/php/Provider/Daisycon/ProductMapping.php b/src/php/Provider/Daisycon/ProductMapping.php
new file mode 100644
index 0000000..862a5d5
--- /dev/null
+++ b/src/php/Provider/Daisycon/ProductMapping.php
@@ -0,0 +1,45 @@
+product_xml = $product_xml;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function get_product_mapping(): array {
+ return [
+ 'sku' => (string)$this->product_xml->sku,
+ 'product_name' => (string)$this->product_xml->title,
+ 'product_price' => number_format((float)$this->product_xml->price, 2,'.', ''),
+ 'product_original_price' => number_format((float)$this->product_xml->price_old, 2,'.', ''),
+ 'product_currency' => (string)$this->product_xml->currency,
+ 'product_url' => sanitize_url((string)$this->product_xml->link),
+ 'product_description' => (string)$this->product_xml->description,
+ 'product_ean' => (string)$this->product_xml->ean,
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function get_product_images(): array {
+ return array_map(function($image) {
+ return (string)$image->image->location;
+ }, iterator_to_array($this->product_xml->images));
+ }
+}
diff --git a/src/php/Provider/ProductMapInterface.php b/src/php/Provider/ProductMapInterface.php
index 498b5a8..7adacbe 100644
--- a/src/php/Provider/ProductMapInterface.php
+++ b/src/php/Provider/ProductMapInterface.php
@@ -15,7 +15,6 @@ public function __construct(SimpleXMLElement $product_xml);
* Get normalized array mapping.
*
* @return array:
- * - 'feed_id': int
* - 'campaign_id': int|null
* - 'product_id': int|null
* - 'sku': string|null
@@ -25,7 +24,6 @@ public function __construct(SimpleXMLElement $product_xml);
* - 'product_url': string
* - 'product_description': string
* - 'product_ean': string|null
- * - 'imported_at': string (datetime in 'Y-m-d H:i:s' format)
*/
public function get_product_mapping(): array;