2 Commits

Author SHA1 Message Date
779fb54995 Add random products shortcode feature
All checks were successful
Create Release / build (push) Successful in 5s
- Add [fourthwall_random] shortcode for displaying random products
- Support for specifying count of products to display
- Option to use all store products or specific product URLs
- Add fwembed_extract_product_urls() function to extract product URLs
- Add fwembed_get_random_products() function for randomization logic
- Update README.md with comprehensive documentation and examples
- Maintain backward compatibility with existing shortcodes
2025-06-24 11:36:36 -07:00
64afcb71cb Refactor plugin with improved architecture and new features
All checks were successful
Create Release / build (push) Successful in 5s
- Eliminated code duplication by creating common HTTP request function
- Added configurable SSL verification setting for development/production
- Implemented smart caching system with 1-hour cache duration
- Enhanced admin interface with cache management and better styling
- Improved error handling and user experience
- Added comprehensive documentation and troubleshooting guide
- Updated README with new features and configuration options
2025-06-24 11:21:04 -07:00
5 changed files with 469 additions and 119 deletions

114
README.md
View File

@@ -1,6 +1,71 @@
# fourth-wall-embed-wp # Fourthwall Store Embed WordPress Plugin
A WordPress Plugin to embed a Fourthwall store or individual products. A WordPress plugin to embed Fourthwall Store products in your WordPress site.
## Shortcodes
### Display All Store Products
```
[fourthwall]
```
Displays all products from your Fourthwall store.
### Display Single Product
```
[fourthwall_single url="https://your-store.com/product-url" show_description="true"]
```
Displays a single product with optional description.
**Parameters:**
- `url` (required): The full URL of the product
- `show_description` (optional): Set to "true" to show product description
### Display Random Products
```
[fourthwall_random count="5"]
```
Displays a random selection of products from your store.
**Parameters:**
- `count` (optional): Number of products to display (default: 3)
- `urls` (optional): Comma-separated list of specific product URLs to randomize from
- `store_url` (optional): Custom store URL (uses default from settings if not provided)
#### Examples:
**Random 5 products from all store products:**
```
[fourthwall_random count="5"]
```
**Random 3 products from specific URLs:**
```
[fourthwall_random count="3" urls="https://store.com/product1,https://store.com/product2,https://store.com/product3"]
```
**Random 2 products from a different store:**
```
[fourthwall_random count="2" store_url="https://different-store.com"]
```
## Installation
1. Upload the plugin files to `/wp-content/plugins/fourth-wall-embed-wp/`
2. Activate the plugin through the 'Plugins' menu in WordPress
3. Go to Settings > Fourthwall Store Embed to configure your store URL
## Configuration
In the WordPress admin, go to Settings > Fourthwall Store Embed to set your Fourthwall store URL.
## Features
- Caches requests for better performance
- Responsive design
- SSL verification options
- Error handling for failed requests
- Random product selection
- Support for multiple store URLs
### How to use the plugin ### How to use the plugin
@@ -8,6 +73,18 @@ A WordPress Plugin to embed a Fourthwall store or individual products.
* Upload the Plugin to WordPress * Upload the Plugin to WordPress
* In the WordPress Dashboard, Navigate to the Fourthwall settings page, and paste your store URL. * In the WordPress Dashboard, Navigate to the Fourthwall settings page, and paste your store URL.
#### Configuration Options
**Store URL**: Enter your Fourthwall store URL (e.g., `https://your-store.fourthwall.com`)
**SSL Verification**:
- **Enabled (Recommended)**: Use for production sites to ensure secure connections
- **Disabled**: Use only for local development when SSL certificates are not properly configured
**Cache Management**:
- Content is automatically cached for 1 hour to improve performance
- Use the "Clear Cache" button if products are not updating
#### Display your entire store #### Display your entire store
To display your entire Fourthwall store on a page, use the shortcode: To display your entire Fourthwall store on a page, use the shortcode:
```[fourthwall]``` ```[fourthwall]```
@@ -19,10 +96,43 @@ To display an individual product from your Fourthwall store, use the shortcode w
You can also display the product description by setting the `show_description` attribute to "true": You can also display the product description by setting the `show_description` attribute to "true":
```[fourthwall_single url="https://your-store.fourthwall.com/products/product-name" show_description="true"]``` ```[fourthwall_single url="https://your-store.fourthwall.com/products/product-name" show_description="true"]```
### Features
- **Smart Caching**: Automatic caching system reduces server load and improves performance
- **Configurable SSL**: Toggle SSL verification for development vs production environments
- **Error Handling**: Graceful fallbacks and clear error messages
- **Admin Interface**: User-friendly settings page with clear instructions
- **Cache Management**: Manual cache clearing for troubleshooting
- **Responsive Design**: CSS styling for both frontend and admin areas
### Requirements ### Requirements
* WordPress 6.0 or higher * WordPress 6.0 or higher
* PHP 7.4 or higher * PHP 7.4 or higher
* Active Fourthwall store * Active Fourthwall store
* cURL extension enabled
### Performance Notes
- Content is cached for 1 hour to reduce API calls to Fourthwall
- Cache automatically refreshes when content changes
- Manual cache clearing available in admin settings
- SSL verification can be disabled for local development
### Troubleshooting
**Products not updating?**
- Clear the cache using the "Clear Cache" button in admin settings
- Check your store URL is correct
- Verify SSL verification setting matches your environment
**SSL errors in development?**
- Disable SSL verification in admin settings (development only)
- Ensure proper SSL certificates in production
**403 Forbidden errors?**
- Fourthwall may be blocking automated requests
- Check if your store is publicly accessible
- Contact Fourthwall support if issues persist
This plugin allows you to integrate your Fourthwall store directly into your WordPress site, either showing your complete product collection or featuring specific products individually. This plugin allows you to integrate your Fourthwall store directly into your WordPress site, either showing your complete product collection or featuring specific products individually.

View File

@@ -40,3 +40,33 @@
font-size: 0.88em; /* Reduced font size for product titles */ font-size: 0.88em; /* Reduced font size for product titles */
font-weight: bold; font-weight: bold;
} }
/* Admin page styles */
.shortcode-info {
background: #fff;
border: 1px solid #ccd0d4;
border-radius: 4px;
padding: 20px;
margin-top: 20px;
}
.shortcode-info h3 {
margin-top: 0;
color: #23282d;
}
.shortcode-info code {
background: #f1f1f1;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Courier New', monospace;
}
.shortcode-info p {
margin: 10px 0;
}
.shortcode-info em {
color: #666;
font-style: italic;
}

View File

@@ -17,3 +17,4 @@ function set_fw_store_embed_css() {
} }
add_action( 'wp_enqueue_scripts', 'set_fw_store_embed_css' ); add_action( 'wp_enqueue_scripts', 'set_fw_store_embed_css' );
add_action( 'admin_enqueue_scripts', 'set_fw_store_embed_css' );

View File

@@ -45,6 +45,14 @@ class fourthwall_settings {
'fourthwall_settings_name_section' 'fourthwall_settings_name_section'
); );
add_settings_field(
'ssl_verify',
__( 'SSL Verification', 'fourthwall_text_domain' ),
array( $this, 'render_ssl_verify_field' ),
'fourthwall_settings_name',
'fourthwall_settings_name_section'
);
} }
public function fourthwall_page_layout() { public function fourthwall_page_layout() {
@@ -54,6 +62,12 @@ class fourthwall_settings {
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'fourthwall_text_domain' ) ); wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'fourthwall_text_domain' ) );
} }
// Handle cache clearing
if (isset($_POST['clear_cache']) && wp_verify_nonce($_POST['_wpnonce'], 'clear_fwembed_cache')) {
$this->clear_fwembed_cache();
echo '<div class="notice notice-success"><p>' . __('Cache cleared successfully!', 'fourthwall_text_domain') . '</p></div>';
}
// Admin Page Layout // Admin Page Layout
echo '<div class="wrap">' . "\n"; echo '<div class="wrap">' . "\n";
echo ' <h1>' . get_admin_page_title() . '</h1>' . "\n"; echo ' <h1>' . get_admin_page_title() . '</h1>' . "\n";
@@ -64,7 +78,21 @@ class fourthwall_settings {
submit_button(); submit_button();
echo ' </form>' . "\n"; echo ' </form>' . "\n";
echo ' <div><p>To use the fourthwall Embed, use the shortcode [fourthwall]</p></div>';
// Cache clearing form
echo ' <form method="post" style="margin-top: 20px;">' . "\n";
echo ' ' . wp_nonce_field('clear_fwembed_cache', '_wpnonce', true, false) . "\n";
echo ' <input type="submit" name="clear_cache" class="button button-secondary" value="' . __('Clear Cache', 'fourthwall_text_domain') . '">' . "\n";
echo ' <p class="description">' . __('Clear the cached Fourthwall content. Use this if products are not updating.', 'fourthwall_text_domain') . '</p>' . "\n";
echo ' </form>' . "\n";
echo ' <div class="shortcode-info">' . "\n";
echo ' <h3>' . __( 'Shortcode Usage', 'fourthwall_text_domain' ) . '</h3>' . "\n";
echo ' <p><strong>' . __( 'Display entire store:', 'fourthwall_text_domain' ) . '</strong> <code>[fourthwall]</code></p>' . "\n";
echo ' <p><strong>' . __( 'Display single product:', 'fourthwall_text_domain' ) . '</strong> <code>[fourthwall_single url="https://your-store.fourthwall.com/products/product-name"]</code></p>' . "\n";
echo ' <p><strong>' . __( 'With description:', 'fourthwall_text_domain' ) . '</strong> <code>[fourthwall_single url="https://your-store.fourthwall.com/products/product-name" show_description="true"]</code></p>' . "\n";
echo ' <p><em>' . __( 'Note: Disable SSL verification only for local development. Keep enabled for production sites.', 'fourthwall_text_domain' ) . '</em></p>' . "\n";
echo ' </div>' . "\n";
echo '</div>' . "\n"; echo '</div>' . "\n";
} }
@@ -83,6 +111,43 @@ class fourthwall_settings {
} }
function render_ssl_verify_field() {
// Retrieve data from the database.
$options = get_option( 'fourthwall_settings_name' );
// Set default value.
$value = isset( $options['ssl_verify'] ) ? $options['ssl_verify'] : '';
// Field output.
echo '<input type="checkbox" name="fourthwall_settings_name[ssl_verify]" value="1" ' . checked( $value, 1, false ) . '>';
echo '<p class="description">' . __( 'Enable SSL verification', 'fourthwall_text_domain' ) . '</p>';
}
/**
* Clear all Fourthwall embed cache
*/
private function clear_fwembed_cache() {
global $wpdb;
// Delete all transients that start with 'fwembed_'
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
'_transient_fwembed_%'
)
);
// Also delete transient timeouts
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
'_transient_timeout_fwembed_%'
)
);
}
} }
new fourthwall_settings; new fourthwall_settings;

View File

@@ -1,10 +1,22 @@
<?php <?php
function fwembed_parse_html($url = null) { /**
if ($url === null) { * Common function to make HTTP requests to Fourthwall
throw new ValueError("Missing URL"); *
* @param string $url The URL to fetch
* @return array Array containing 'content' and 'error' keys
*/
function fwembed_make_request($url) {
// Check for cached content first
$cache_key = 'fwembed_' . md5($url);
$cached_content = get_transient($cache_key);
if ($cached_content !== false) {
return $cached_content;
} }
$options = get_option('fourthwall_settings_name');
$ssl_verify = isset($options['ssl_verify']) ? $options['ssl_verify'] : true;
$ch = curl_init(); $ch = curl_init();
@@ -24,32 +36,54 @@ function fwembed_parse_html($url = null) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0'); curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0');
curl_setopt($ch, CURLOPT_TIMEOUT, 30); curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Only if necessary for testing curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $ssl_verify);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt'); // Store cookies curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt'); // Use cookies curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
$html_content = curl_exec($ch); $html_content = curl_exec($ch);
$error = null;
$status_code = 0;
if (curl_errno($ch)) { if (curl_errno($ch)) {
$error = curl_error($ch); $error = curl_error($ch);
curl_close($ch); } else {
return "Error fetching URL: " . $error;
}
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status_code == 403) { if ($status_code == 403) {
curl_close($ch); $error = "Access forbidden (403). The website may be blocking automated requests.";
return "Access forbidden (403). The website may be blocking automated requests."; }
} }
curl_close($ch); curl_close($ch);
$result = [
'content' => $html_content,
'error' => $error,
'status_code' => $status_code
];
// Cache the result for 1 hour (3600 seconds) if no error
if (!$error && $status_code == 200) {
set_transient($cache_key, $result, 3600);
}
return $result;
}
/**
* Parse HTML content and extract product tiles
*
* @param string $html_content The HTML content to parse
* @param string $base_url The base URL for constructing links
* @return string Parsed HTML content
*/
function fwembed_parse_product_tiles($html_content, $base_url) {
$html = null; $html = null;
libxml_use_internal_errors(true); libxml_use_internal_errors(true);
$dom = new DOMDocument(); $dom = new DOMDocument();
@$dom->loadHTML(loadHTML5($html_content), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); @$dom->loadHTML(loadHTML5($html_content), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$dom->documentURI = $url; $dom->documentURI = $base_url;
$divs = $dom->getElementsByTagName('div'); $divs = $dom->getElementsByTagName('div');
foreach ($divs as $div) { foreach ($divs as $div) {
if ($div->hasAttribute('data-testid') && $div->getAttribute('data-testid') === 'product') { if ($div->hasAttribute('data-testid') && $div->getAttribute('data-testid') === 'product') {
$xpath = new DOMXPath($dom); $xpath = new DOMXPath($dom);
@@ -62,10 +96,7 @@ function fwembed_parse_html($url = null) {
$tileDesc = $xpath->query('.//*[contains(@class, "tile__description") $tileDesc = $xpath->query('.//*[contains(@class, "tile__description")
and not(contains(@class, "badge")) and not(contains(@class, "badge"))
and not(contains(@class, "tile_options"))]', $div); and not(contains(@class, "tile_options"))]', $div);
$tilePrices = $xpath->query('.//*[contains(@class, "tile__prices")
and not(contains(@class, "badge"))
and not(contains(@class, "tile_options"))
and not(contains(@class, "tile__price tile__price--original"))]', $div);
$productHTML = ''; $productHTML = '';
$linkHref = ''; $linkHref = '';
if ($tileLink->length > 0) { if ($tileLink->length > 0) {
@@ -77,17 +108,28 @@ function fwembed_parse_html($url = null) {
if ($tileDesc->length > 0) { if ($tileDesc->length > 0) {
$productHTML .= $dom->saveHTML($tileDesc->item(0)); $productHTML .= $dom->saveHTML($tileDesc->item(0));
} }
//if ($tilePrices->length > 0) {
// $productHTML .= $dom->saveHTML($tilePrices->item(0));
// }
$html = $html . '<div class="product-tile"><a class="product-link" target="_blank" href="' . $url . $linkHref . '">' . $productHTML . '</a></div>'; $html = $html . '<div class="product-tile"><a class="product-link" target="_blank" href="' . $base_url . $linkHref . '">' . $productHTML . '</a></div>';
} }
} }
libxml_clear_errors(); libxml_clear_errors();
return $html; return $html;
} }
function fwembed_parse_html($url = null) {
if ($url === null) {
throw new ValueError("Missing URL");
}
$result = fwembed_make_request($url);
if ($result['error']) {
return "Error fetching URL: " . $result['error'];
}
return fwembed_parse_product_tiles($result['content'], $url);
}
function loadHTML5($html) { function loadHTML5($html) {
return '<!DOCTYPE html><html><body>' . $html . '</body></html>'; return '<!DOCTYPE html><html><body>' . $html . '</body></html>';
} }
@@ -100,49 +142,15 @@ function fwembed_shortcode( $atts ) {
return $store_render; return $store_render;
} }
function fwembed_parse_html_single($url = null, $show_description = false) { /**
if ($url === null) { * Parse HTML content and extract single product information
throw new ValueError("Missing URL"); *
} * @param string $html_content The HTML content to parse
* @param string $url The product URL
$ch = curl_init(); * @param bool $show_description Whether to show product description
* @return string Parsed HTML content
// More complete browser-like headers */
$headers = [ function fwembed_parse_single_product($html_content, $url, $show_description = false) {
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language: en-US,en;q=0.5',
'Connection: keep-alive',
'Upgrade-Insecure-Requests: 1',
'Cache-Control: max-age=0'
];
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_ENCODING, "");
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0');
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Only if necessary for testing
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt'); // Store cookies
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt'); // Use cookies
$html_content = curl_exec($ch);
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
return "Error fetching URL: " . $error;
}
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status_code == 403) {
curl_close($ch);
return "Access forbidden (403). The website may be blocking automated requests.";
}
curl_close($ch);
$html = null; $html = null;
libxml_use_internal_errors(true); libxml_use_internal_errors(true);
$dom = new DOMDocument(); $dom = new DOMDocument();
@@ -191,6 +199,20 @@ function fwembed_parse_html_single($url = null, $show_description = false) {
return $html; return $html;
} }
function fwembed_parse_html_single($url = null, $show_description = false) {
if ($url === null) {
throw new ValueError("Missing URL");
}
$result = fwembed_make_request($url);
if ($result['error']) {
return "Error fetching URL: " . $result['error'];
}
return fwembed_parse_single_product($result['content'], $url, $show_description);
}
function fwembed_single_shortcode($atts) { function fwembed_single_shortcode($atts) {
$atts = shortcode_atts( $atts = shortcode_atts(
array( array(
@@ -212,3 +234,125 @@ function fwembed_single_shortcode($atts) {
} }
add_shortcode('fourthwall_single', 'fwembed_single_shortcode'); add_shortcode('fourthwall_single', 'fwembed_single_shortcode');
add_shortcode( 'fourthwall', 'fwembed_shortcode' ); add_shortcode( 'fourthwall', 'fwembed_shortcode' );
add_shortcode( 'fourthwall_random', 'fwembed_random_shortcode' );
/**
* Extract all product URLs from a store page
*
* @param string $html_content The HTML content to parse
* @param string $base_url The base URL for constructing links
* @return array Array of product URLs
*/
function fwembed_extract_product_urls($html_content, $base_url) {
$product_urls = array();
libxml_use_internal_errors(true);
$dom = new DOMDocument();
@$dom->loadHTML(loadHTML5($html_content), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$dom->documentURI = $base_url;
$divs = $dom->getElementsByTagName('div');
foreach ($divs as $div) {
if ($div->hasAttribute('data-testid') && $div->getAttribute('data-testid') === 'product') {
$xpath = new DOMXPath($dom);
$tileLink = $xpath->query('.//a[contains(@class, "tile")]', $div);
if ($tileLink->length > 0) {
$linkHref = $tileLink->item(0)->getAttribute('href');
$full_url = $base_url . $linkHref;
$product_urls[] = $full_url;
}
}
}
libxml_clear_errors();
return $product_urls;
}
/**
* Get random products from store or specified URLs
*
* @param string $store_url The store URL to fetch products from (optional if urls provided)
* @param array $urls Array of specific product URLs to randomize from
* @param int $count Number of products to display
* @return string HTML content of random products
*/
function fwembed_get_random_products($store_url = null, $urls = array(), $count = 3) {
$product_urls = array();
// If specific URLs are provided, use those
if (!empty($urls)) {
$product_urls = $urls;
}
// Otherwise, fetch all products from the store
elseif ($store_url) {
$result = fwembed_make_request($store_url);
if ($result['error']) {
return "Error fetching store URL: " . $result['error'];
}
$product_urls = fwembed_extract_product_urls($result['content'], $store_url);
} else {
return "Error: Either store URL or product URLs must be provided";
}
// Shuffle the URLs to randomize
shuffle($product_urls);
// Limit to requested count
$selected_urls = array_slice($product_urls, 0, $count);
$html = '';
foreach ($selected_urls as $url) {
$product_html = fwembed_parse_html_single($url, false);
if ($product_html && !strpos($product_html, 'Error fetching URL')) {
$html .= $product_html;
}
}
return $html;
}
function fwembed_random_shortcode($atts) {
$atts = shortcode_atts(
array(
'count' => '3',
'urls' => '',
'store_url' => '',
),
$atts
);
$count = intval($atts['count']);
if ($count <= 0) {
$count = 3;
}
$urls = array();
if (!empty($atts['urls'])) {
// Split URLs by comma and clean them up
$url_array = explode(',', $atts['urls']);
foreach ($url_array as $url) {
$clean_url = trim($url);
if (!empty($clean_url)) {
$urls[] = $clean_url;
}
}
}
$store_url = '';
if (!empty($atts['store_url'])) {
$store_url = trim($atts['store_url']);
} else {
// Use default store URL from settings if no store_url provided
$options = get_option('fourthwall_settings_name');
$store_url = isset($options['fourth_url']) ? $options['fourth_url'] : '';
}
$products_html = fwembed_get_random_products($store_url, $urls, $count);
if (empty($products_html)) {
return '<p>No products found to display.</p>';
}
return '<div class="fw-random-products">' . PHP_EOL . $products_html . PHP_EOL . '</div>';
}