Compare commits
No commits in common. "main" and "auto-update" have entirely different histories.
main
...
auto-updat
@ -25,6 +25,7 @@ jobs:
|
|||||||
echo "version=$(date +'%Y.%m.%d-%H%M')" >> $GITHUB_OUTPUT
|
echo "version=$(date +'%Y.%m.%d-%H%M')" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# NEW STEP: Generate release notes from commits
|
||||||
- name: Generate release notes
|
- name: Generate release notes
|
||||||
id: release_notes
|
id: release_notes
|
||||||
run: |
|
run: |
|
||||||
@ -39,11 +40,18 @@ jobs:
|
|||||||
COMMITS=$(git log --pretty=format:"* %s (%h)" ${LATEST_TAG}..HEAD --no-merges)
|
COMMITS=$(git log --pretty=format:"* %s (%h)" ${LATEST_TAG}..HEAD --no-merges)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create release notes with header (without encoding newlines)
|
# Escape newlines and special characters for GitHub Actions
|
||||||
|
COMMITS="${COMMITS//'%'/'%25'}"
|
||||||
|
COMMITS="${COMMITS//$'\n'/'%0A'}"
|
||||||
|
COMMITS="${COMMITS//$'\r'/'%0D'}"
|
||||||
|
|
||||||
|
# Create release notes with header
|
||||||
|
NOTES="## What's New in ${{ steps.get_version.outputs.version }}%0A%0A"
|
||||||
|
NOTES+="$COMMITS"
|
||||||
|
|
||||||
|
# Output to GitHub Actions
|
||||||
echo "notes<<EOF" >> $GITHUB_OUTPUT
|
echo "notes<<EOF" >> $GITHUB_OUTPUT
|
||||||
echo "## What's New in ${{ steps.get_version.outputs.version }}" >> $GITHUB_OUTPUT
|
echo "$NOTES" >> $GITHUB_OUTPUT
|
||||||
echo "" >> $GITHUB_OUTPUT
|
|
||||||
echo "$COMMITS" >> $GITHUB_OUTPUT
|
|
||||||
echo "EOF" >> $GITHUB_OUTPUT
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Update plugin version
|
- name: Update plugin version
|
||||||
@ -55,24 +63,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Create ZIP archive
|
- name: Create ZIP archive
|
||||||
run: |
|
run: |
|
||||||
# Create a temp directory with the correct plugin folder name
|
zip -r fourthwall-store-embed.zip . -x ".git/*" ".gitea/*"
|
||||||
mkdir -p /tmp/fourthwall-store-embed
|
|
||||||
|
|
||||||
# Copy files to the temp directory (excluding git and other unnecessary files)
|
|
||||||
cp -r * /tmp/fourthwall-store-embed/ 2>/dev/null || true
|
|
||||||
|
|
||||||
# Exclude .git and .gitea directories
|
|
||||||
rm -rf /tmp/fourthwall-store-embed/.git /tmp/fourthwall-store-embed/.gitea 2>/dev/null || true
|
|
||||||
|
|
||||||
# Create the ZIP file with the proper structure
|
|
||||||
cd /tmp
|
|
||||||
zip -r $GITHUB_WORKSPACE/fourthwall-store-embed.zip fourthwall-store-embed
|
|
||||||
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
token: "${{ secrets.REPO_TOKEN }}"
|
token: "${{ secrets.REPO_TOKEN }}"
|
||||||
title: "Fourthwall-store-embed Release ${{ steps.get_version.outputs.version }}"
|
title: Fourthwall-store-embed Release ${{ steps.get_version.outputs.version }}
|
||||||
tag_name: ${{ steps.get_version.outputs.version }}
|
tag_name: ${{ steps.get_version.outputs.version }}
|
||||||
body: "${{ steps.release_notes.outputs.notes }}"
|
body: "${{ steps.release_notes.outputs.notes }}"
|
||||||
files: |
|
files: |
|
||||||
|
@ -35,10 +35,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Create plugin zip
|
- name: Create plugin zip
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /tmp/fourthwall-store-embed
|
mkdir -p build
|
||||||
rsync -av --exclude=".git" --exclude=".gitea" --exclude="build" . /tmp/fourthwall-store-embed/
|
zip -r build/fourthwall-store-embed.zip . -x ".git/*" ".gitea/*" "build/*" "*.git*"
|
||||||
cd /tmp
|
|
||||||
zip -r $GITEA_WORK_DIR/fourthwall-store-embed.zip fourthwall-store-embed
|
|
||||||
|
|
||||||
- name: Upload zip to release
|
- name: Upload zip to release
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
|
137
README.md
137
README.md
@ -1,139 +1,12 @@
|
|||||||
# Fourthwall Store Embed WordPress Plugin
|
# fourth-wall-embed-wp
|
||||||
|
|
||||||
A WordPress plugin to embed Fourthwall Store products in your WordPress site.
|
A WordPress Plugin to embed a fourth wall embed.
|
||||||
|
|
||||||
## 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
|
||||||
|
|
||||||
* Download the latest release from [releases](https://repo.anhonesthost.net/CyberCoveLLC/fourth-wall-embed-wp/releases)
|
* Download the latest release from [releases](https://repo.anhonesthost.net/CyberCoveLLC/fourth-wall-embed-wp/releases)
|
||||||
* 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.
|
||||||
|
* On the page you want have your store displayed, add the shortcode ```[fourthwall]```
|
||||||
|
|
||||||
#### Configuration Options
|
This is an early release of the plugin, and if you can think of things that could be improved or find any bugs, please open an issue on the repository.
|
||||||
|
|
||||||
**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]```
|
|
||||||
|
|
||||||
#### Display a single product
|
|
||||||
To display an individual product from your Fourthwall store, use the shortcode with the product URL:
|
|
||||||
```[fourthwall_single url="https://your-store.fourthwall.com/products/product-name"]```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
If you can think of things that could be improved or find any bugs, please open an issue on the repository.
|
|
@ -40,33 +40,3 @@
|
|||||||
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;
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Plugin Name: Fourthwall Store Embed
|
* Plugin Name: Fourthwall Store Embed
|
||||||
* Plugin URL: https://darksideofperfection.com/
|
* Plugin URL: https://cybercove.io/
|
||||||
* Description: Embed Fourthwall Store in WordPress
|
* Description: Embed Fourthwall Store in WordPress
|
||||||
* Version: {auto_update_value_on_deploy}
|
* Version: {auto_update_value_on_deploy}
|
||||||
* Author: Joshua Knapp
|
* Author: Joshua Knapp
|
||||||
@ -17,4 +17,3 @@ 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' );
|
|
||||||
|
@ -48,6 +48,11 @@ function fwembed_check_for_updates() {
|
|||||||
if ($response) {
|
if ($response) {
|
||||||
$release_info = json_decode($response);
|
$release_info = json_decode($response);
|
||||||
|
|
||||||
|
// Update debug info with latest version
|
||||||
|
$debug_data['latest_version'] = $release_info->tag_name ?? 'Not found';
|
||||||
|
$debug_data['response'] = substr(print_r($release_info, true), 0, 1000);
|
||||||
|
update_option('fwembed_update_debug', $debug_data);
|
||||||
|
|
||||||
// Get latest version from tag name
|
// Get latest version from tag name
|
||||||
$latest_version = $release_info->tag_name;
|
$latest_version = $release_info->tag_name;
|
||||||
|
|
||||||
@ -91,8 +96,8 @@ function fwembed_check_for_updates() {
|
|||||||
'package' => $download_url,
|
'package' => $download_url,
|
||||||
'icons' => [],
|
'icons' => [],
|
||||||
'banners' => [],
|
'banners' => [],
|
||||||
'tested' => '6.8',
|
'tested' => '6.4.2',
|
||||||
'requires' => '6.2',
|
'requires' => '5.0',
|
||||||
'compatibility' => new stdClass(),
|
'compatibility' => new stdClass(),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -103,6 +108,23 @@ function fwembed_check_for_updates() {
|
|||||||
}
|
}
|
||||||
add_action('admin_init', 'fwembed_check_for_updates');
|
add_action('admin_init', 'fwembed_check_for_updates');
|
||||||
|
|
||||||
|
// Add admin notice for debugging
|
||||||
|
add_action('admin_notices', function() {
|
||||||
|
if (current_user_can('manage_options')) {
|
||||||
|
$debug = get_option('fwembed_update_debug', []);
|
||||||
|
echo '<div class="notice notice-info is-dismissible"><p>Update Debug: Current: ' .
|
||||||
|
esc_html($debug['current_version'] ?? 'N/A') . ', Latest: ' .
|
||||||
|
esc_html($debug['latest_version'] ?? 'N/A') . '</p>';
|
||||||
|
|
||||||
|
// Detailed debug info with query param
|
||||||
|
if (isset($_GET['fwembed_debug'])) {
|
||||||
|
echo '<pre>' . esc_html(print_r($debug, true)) . '</pre>';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '</div>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// Add plugin information for the details popup/changelog
|
// Add plugin information for the details popup/changelog
|
||||||
add_filter('plugins_api', function($result, $action, $args) {
|
add_filter('plugins_api', function($result, $action, $args) {
|
||||||
|
@ -45,14 +45,6 @@ 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() {
|
||||||
@ -62,12 +54,6 @@ 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";
|
||||||
@ -78,24 +64,7 @@ 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><strong>' . __( 'Display random products:', 'fourthwall_text_domain' ) . '</strong> <code>[fourthwall_random count="5"]</code></p>' . "\n";
|
|
||||||
echo ' <p><strong>' . __( 'Random from specific URLs:', 'fourthwall_text_domain' ) . '</strong> <code>[fourthwall_random count="3" urls="https://store.com/product1,https://store.com/product2,https://store.com/product3"]</code></p>' . "\n";
|
|
||||||
echo ' <p><strong>' . __( 'Random from different store:', 'fourthwall_text_domain' ) . '</strong> <code>[fourthwall_random count="2" store_url="https://different-store.fourthwall.com"]</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";
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -114,43 +83,6 @@ 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;
|
||||||
|
@ -1,358 +1,102 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
$options = get_option('fourthwall_settings_name');
|
|
||||||
$ssl_verify = isset($options['ssl_verify']) ? $options['ssl_verify'] : true;
|
|
||||||
|
|
||||||
$ch = curl_init();
|
|
||||||
|
|
||||||
// More complete browser-like headers
|
|
||||||
$headers = [
|
|
||||||
'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, $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);
|
|
||||||
} 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 . '<div class="product-tile"><a class="product-link" target="_blank" href="' . $base_url . $linkHref . '">' . $productHTML . '</a></div>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
libxml_clear_errors();
|
|
||||||
return $html;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fwembed_parse_html($url = null) {
|
function fwembed_parse_html($url = null) {
|
||||||
if ($url === null) {
|
if ($url === null) {
|
||||||
throw new ValueError("Missing URL");
|
throw new ValueError("Missing URL");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
|
||||||
|
// More complete browser-like headers
|
||||||
|
$headers = [
|
||||||
|
'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;
|
||||||
|
libxml_use_internal_errors(true);
|
||||||
|
$dom = new DOMDocument();
|
||||||
|
@$dom->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 . '<div class="product-tile"><a class="product-link" target="_blank" href="' . $url . $linkHref . '">' . $productHTML . '</a></div>';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
$result = fwembed_make_request($url);
|
libxml_clear_errors();
|
||||||
|
return $html;
|
||||||
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>';
|
||||||
}
|
}
|
||||||
|
|
||||||
function fwembed_shortcode( $atts ) {
|
function fwembed_shortcode( $atts ) {
|
||||||
$options = get_option( 'fourthwall_settings_name' );
|
$options = get_option( 'fourthwall_settings_name' );
|
||||||
$value = isset( $options['fourth_url'] ) ? $options['fourth_url'] : 'https://fourthwall.com';
|
$value = isset( $options['fourth_url'] ) ? $options['fourth_url'] : 'https://fourthwall.com';
|
||||||
$store_html = fwembed_parse_html($value);
|
$store_html = fwembed_parse_html($value);
|
||||||
$store_render = '<div class="fw-store-parent">' . PHP_EOL . $store_html . PHP_EOL . '</div>';
|
$store_render = '<div class="fw-store-parent">' . PHP_EOL . $store_html . PHP_EOL . '</div>';
|
||||||
return $store_render;
|
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();
|
|
||||||
@$dom->loadHTML(loadHTML5($html_content), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
|
|
||||||
$dom->documentURI = $url;
|
|
||||||
$xpath = new DOMXPath($dom);
|
|
||||||
|
|
||||||
// Extract product information
|
|
||||||
$productTitle = $xpath->query('//h1[@class="product-info__title"]');
|
|
||||||
$productPrice = $xpath->query('//span[@class="product-info__price product-info__price--original"]');
|
|
||||||
$productImage = $xpath->query('//div[@data-gallery="gallery-slide"][1]//img[@class="gallery__image-object"]');
|
|
||||||
$productDesc = $xpath->query('//div[@class="product-info__description"]//div[@class="html-formatter"]');
|
|
||||||
|
|
||||||
if ($productTitle->length > 0 && $productImage->length > 0) {
|
|
||||||
$title = $productTitle->item(0)->textContent;
|
|
||||||
$price = $productPrice->length > 0 ? $productPrice->item(0)->textContent : '';
|
|
||||||
|
|
||||||
// Get image attributes
|
|
||||||
$imageNode = $productImage->item(0);
|
|
||||||
$imageSrc = $imageNode->getAttribute('src');
|
|
||||||
$imageAlt = $imageNode->getAttribute('alt');
|
|
||||||
|
|
||||||
// Build the HTML
|
|
||||||
$html = '<div class="product-tile">';
|
|
||||||
$html .= '<a class="product-link" target="_blank" href="' . $url . '">';
|
|
||||||
$html .= '<img class="tile__item tile__item--1" src="' . $imageSrc . '" alt="' . htmlspecialchars($imageAlt) . '">';
|
|
||||||
|
|
||||||
$html .= '<div class="tile__description">';
|
|
||||||
$html .= '<h3 class="tile__heading">' . htmlspecialchars(trim($title)) . '</h3>';
|
|
||||||
$html .= '<div class="tile__prices">';
|
|
||||||
$html .= '<span class="tile__price tile__price--original">' . trim($price) . '</span>';
|
|
||||||
$html .= '</div>';
|
|
||||||
$html .= '</div>';
|
|
||||||
$html .= '</a>';
|
|
||||||
|
|
||||||
// Add description if show_description is true
|
|
||||||
if ($show_description && $productDesc->length > 0) {
|
|
||||||
$description = $dom->saveHTML($productDesc->item(0));
|
|
||||||
$html .= '<div class="product-description">' . $description . '</div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
$html .= '</div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
libxml_clear_errors();
|
|
||||||
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(
|
|
||||||
'url' => '',
|
|
||||||
'show_description' => 'false',
|
|
||||||
),
|
|
||||||
$atts
|
|
||||||
);
|
|
||||||
|
|
||||||
if (empty($atts['url'])) {
|
|
||||||
return '<p>Error: URL is required for [fourthwall_single] shortcode</p>';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert string 'true'/'false' to boolean
|
|
||||||
$show_description = filter_var($atts['show_description'], FILTER_VALIDATE_BOOLEAN);
|
|
||||||
|
|
||||||
$product_html = fwembed_parse_html_single($atts['url'], $show_description);
|
|
||||||
return '<div class="fw-single-product">' . PHP_EOL . $product_html . PHP_EOL . '</div>';
|
|
||||||
}
|
|
||||||
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>';
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user