diff --git a/README.md b/README.md index 599d8a0..0a6f37e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # fourth-wall-embed-wp -A WordPress Plugin to embed a Fourthwall store or individual products. +A WordPress Plugin to embed a Fourthwall store or individual products with advanced caching and configuration options. ### How to use the plugin @@ -8,6 +8,18 @@ A WordPress Plugin to embed a Fourthwall store or individual products. * Upload the Plugin to WordPress * 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 To display your entire Fourthwall store on a page, use the shortcode: ```[fourthwall]``` @@ -19,10 +31,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": ```[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 * WordPress 6.0 or higher * PHP 7.4 or higher * 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. diff --git a/css/fw-store-embed.css b/css/fw-store-embed.css index 0ebd000..e132e2b 100644 --- a/css/fw-store-embed.css +++ b/css/fw-store-embed.css @@ -39,4 +39,34 @@ .tile__heading { font-size: 0.88em; /* Reduced font size for product titles */ 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; } \ No newline at end of file diff --git a/fw-store-embed.php b/fw-store-embed.php index d6dbf6b..c5fce74 100644 --- a/fw-store-embed.php +++ b/fw-store-embed.php @@ -17,3 +17,4 @@ function 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' ); diff --git a/libs/settings.php b/libs/settings.php index ead56c3..950ddd3 100644 --- a/libs/settings.php +++ b/libs/settings.php @@ -45,6 +45,14 @@ class fourthwall_settings { '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() { @@ -54,6 +62,12 @@ class fourthwall_settings { 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 '

' . __('Cache cleared successfully!', 'fourthwall_text_domain') . '

'; + } + // Admin Page Layout echo '
' . "\n"; echo '

' . get_admin_page_title() . '

' . "\n"; @@ -64,7 +78,21 @@ class fourthwall_settings { submit_button(); echo ' ' . "\n"; - echo '

To use the fourthwall Embed, use the shortcode [fourthwall]

'; + + // Cache clearing form + echo '
' . "\n"; + echo ' ' . wp_nonce_field('clear_fwembed_cache', '_wpnonce', true, false) . "\n"; + echo ' ' . "\n"; + echo '

' . __('Clear the cached Fourthwall content. Use this if products are not updating.', 'fourthwall_text_domain') . '

' . "\n"; + echo '
' . "\n"; + + echo '
' . "\n"; + echo '

' . __( 'Shortcode Usage', 'fourthwall_text_domain' ) . '

' . "\n"; + echo '

' . __( 'Display entire store:', 'fourthwall_text_domain' ) . ' [fourthwall]

' . "\n"; + echo '

' . __( 'Display single product:', 'fourthwall_text_domain' ) . ' [fourthwall_single url="https://your-store.fourthwall.com/products/product-name"]

' . "\n"; + echo '

' . __( 'With description:', 'fourthwall_text_domain' ) . ' [fourthwall_single url="https://your-store.fourthwall.com/products/product-name" show_description="true"]

' . "\n"; + echo '

' . __( 'Note: Disable SSL verification only for local development. Keep enabled for production sites.', 'fourthwall_text_domain' ) . '

' . "\n"; + echo '
' . "\n"; echo '
' . "\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 ''; + echo '

' . __( 'Enable SSL verification', 'fourthwall_text_domain' ) . '

'; + + } + + /** + * 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; diff --git a/libs/shortcode.php b/libs/shortcode.php index a898b22..284d9f8 100644 --- a/libs/shortcode.php +++ b/libs/shortcode.php @@ -1,110 +1,23 @@ loadHTML(loadHTML5($html_content), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); - $dom->documentURI = $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); - $tileItem = $xpath->query('.//img[contains(@class, "tile__item--1") - and not(contains(@class, "badge")) - and not(contains(@class, "tile_options")) - and not(contains(@class, "tile__item--2"))]', $div); - $tileDesc = $xpath->query('.//*[contains(@class, "tile__description") - and not(contains(@class, "badge")) - 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 = ''; - $linkHref = ''; - if ($tileLink->length > 0) { - $linkHref = $tileLink->item(0)->getAttribute('href'); - } - if ($tileItem->length > 0) { - $productHTML .= $dom->saveHTML($tileItem->item(0)); - } - if ($tileDesc->length > 0) { - $productHTML .= $dom->saveHTML($tileDesc->item(0)); - } - //if ($tilePrices->length > 0) { - // $productHTML .= $dom->saveHTML($tilePrices->item(0)); - // } - - $html = $html . '
' . $productHTML . '
'; +/** + * Common function to make HTTP requests to Fourthwall + * + * @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; } - } - libxml_clear_errors(); - return $html; -} - -function loadHTML5($html) { - return '' . $html . ''; -} - -function fwembed_shortcode( $atts ) { - $options = get_option( 'fourthwall_settings_name' ); - $value = isset( $options['fourth_url'] ) ? $options['fourth_url'] : 'https://fourthwall.com'; - $store_html = fwembed_parse_html($value); - $store_render = '
' . PHP_EOL . $store_html . PHP_EOL . '
'; - return $store_render; -} - -function fwembed_parse_html_single($url = null, $show_description = false) { - if ($url === null) { - throw new ValueError("Missing URL"); - } - + + $options = get_option('fourthwall_settings_name'); + $ssl_verify = isset($options['ssl_verify']) ? $options['ssl_verify'] : true; + $ch = curl_init(); // More complete browser-like headers @@ -123,26 +36,121 @@ function fwembed_parse_html_single($url = null, $show_description = false) { 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 - + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $ssl_verify); + curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt'); + curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt'); + $html_content = curl_exec($ch); + $error = null; + $status_code = 0; 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."; + } else { + $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + if ($status_code == 403) { + $error = "Access forbidden (403). The website may be blocking automated requests."; + } } 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; + 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); + $tileItem = $xpath->query('.//img[contains(@class, "tile__item--1") + and not(contains(@class, "badge")) + and not(contains(@class, "tile_options")) + and not(contains(@class, "tile__item--2"))]', $div); + $tileDesc = $xpath->query('.//*[contains(@class, "tile__description") + and not(contains(@class, "badge")) + and not(contains(@class, "tile_options"))]', $div); + + $productHTML = ''; + $linkHref = ''; + if ($tileLink->length > 0) { + $linkHref = $tileLink->item(0)->getAttribute('href'); + } + if ($tileItem->length > 0) { + $productHTML .= $dom->saveHTML($tileItem->item(0)); + } + if ($tileDesc->length > 0) { + $productHTML .= $dom->saveHTML($tileDesc->item(0)); + } + + $html = $html . '
' . $productHTML . '
'; + } + } + libxml_clear_errors(); + 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) { + return '' . $html . ''; +} + +function fwembed_shortcode( $atts ) { + $options = get_option( 'fourthwall_settings_name' ); + $value = isset( $options['fourth_url'] ) ? $options['fourth_url'] : 'https://fourthwall.com'; + $store_html = fwembed_parse_html($value); + $store_render = '
' . PHP_EOL . $store_html . PHP_EOL . '
'; + return $store_render; +} + +/** + * Parse HTML content and extract single product information + * + * @param string $html_content The HTML content to parse + * @param string $url The product URL + * @param bool $show_description Whether to show product description + * @return string Parsed HTML content + */ +function fwembed_parse_single_product($html_content, $url, $show_description = false) { $html = null; libxml_use_internal_errors(true); $dom = new DOMDocument(); @@ -191,6 +199,20 @@ function fwembed_parse_html_single($url = null, $show_description = false) { 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) { $atts = shortcode_atts( array(