Compare commits
18 Commits
2025.04.22
...
2025.10.05
Author | SHA1 | Date | |
---|---|---|---|
9b4e28ece1 | |||
b4f17c80b3 | |||
779fb54995 | |||
64afcb71cb | |||
94329dc91a | |||
ca3275fb4b | |||
609883fc7f | |||
60d5060398 | |||
49703fd66d | |||
9e1d8b8684 | |||
554e2ba93e | |||
1955b14ac5 | |||
10634e8f7e | |||
e94ef7b84b | |||
db1d5f8945 | |||
10ec29c30a | |||
bf99f442ba | |||
0d0cb65633 |
@@ -14,6 +14,7 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.CI_USER }}
|
||||
password: ${{ secrets.CI_TOKEN }}
|
||||
fetch-depth: 0 # Important: Fetch all history for commit messages
|
||||
|
||||
- name: Get version
|
||||
id: get_version
|
||||
@@ -24,15 +25,55 @@ jobs:
|
||||
echo "version=$(date +'%Y.%m.%d-%H%M')" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Generate release notes
|
||||
id: release_notes
|
||||
run: |
|
||||
# Find the most recent tag
|
||||
LATEST_TAG=$(git describe --tags --abbrev=0 --always 2>/dev/null || echo "none")
|
||||
|
||||
if [ "$LATEST_TAG" = "none" ]; then
|
||||
# If no previous tag exists, get all commits
|
||||
COMMITS=$(git log --pretty=format:"* %s (%h)" --no-merges)
|
||||
else
|
||||
# Get commits since the last tag
|
||||
COMMITS=$(git log --pretty=format:"* %s (%h)" ${LATEST_TAG}..HEAD --no-merges)
|
||||
fi
|
||||
|
||||
# Create release notes with header (without encoding newlines)
|
||||
echo "notes<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "## What's New in ${{ steps.get_version.outputs.version }}" >> $GITHUB_OUTPUT
|
||||
echo "" >> $GITHUB_OUTPUT
|
||||
echo "$COMMITS" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update plugin version
|
||||
run: |
|
||||
# Replace version placeholder with actual version
|
||||
sed -i "s/Version: {auto_update_value_on_deploy}/Version: ${{ steps.get_version.outputs.version }}/" fw-store-embed.php
|
||||
# Verify the change was made
|
||||
grep "Version:" fw-store-embed.php
|
||||
|
||||
- name: Create ZIP archive
|
||||
run: |
|
||||
zip -r fourthwall-store-embed.zip . -x ".git/*" ".gitea/*"
|
||||
# Create a temp directory with the correct plugin folder name
|
||||
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
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
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 }}
|
||||
body: "${{ steps.release_notes.outputs.notes }}"
|
||||
files: |
|
||||
fourthwall-store-embed.zip
|
||||
fourthwall-store-embed.zip
|
51
.gitea/workflows/update-version.yml
Normal file
51
.gitea/workflows/update-version.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
name: Update Plugin Version
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created, edited]
|
||||
|
||||
jobs:
|
||||
update-version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get release tag
|
||||
id: get_tag
|
||||
run: echo "TAG=${GITEA_REF#refs/tags/}" >> $GITEA_ENV
|
||||
|
||||
- name: Update version in plugin file
|
||||
run: |
|
||||
# Replace version in main plugin file
|
||||
sed -i "s/Version: .*/Version: ${{ env.TAG }}/" fw-store-embed.php
|
||||
|
||||
# Verify change
|
||||
grep "Version:" fw-store-embed.php
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git config --local user.email "action@gitea.com"
|
||||
git config --local user.name "Gitea Action"
|
||||
git add fw-store-embed.php
|
||||
git commit -m "Update version to ${{ env.TAG }}"
|
||||
git push
|
||||
|
||||
- name: Create plugin zip
|
||||
run: |
|
||||
mkdir -p /tmp/fourthwall-store-embed
|
||||
rsync -av --exclude=".git" --exclude=".gitea" --exclude="build" . /tmp/fourthwall-store-embed/
|
||||
cd /tmp
|
||||
zip -r $GITEA_WORK_DIR/fourthwall-store-embed.zip fourthwall-store-embed
|
||||
|
||||
- name: Upload zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ gitea.event.release.upload_url }}
|
||||
asset_path: build/fourthwall-store-embed.zip
|
||||
asset_name: fourthwall-store-embed.zip
|
||||
asset_content_type: application/zip
|
124
CLAUDE.md
Normal file
124
CLAUDE.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Overview
|
||||
|
||||
This is a WordPress plugin that embeds Fourthwall store products into WordPress sites via shortcodes. The plugin fetches product data from Fourthwall stores using cURL, parses HTML to extract product information, and displays them with custom CSS styling.
|
||||
|
||||
## Core Architecture
|
||||
|
||||
### File Structure
|
||||
|
||||
- **fw-store-embed.php** - Main plugin entry point that loads libraries and registers CSS
|
||||
- **libs/settings.php** - Admin settings page, cache management, and WordPress options API integration
|
||||
- **libs/shortcode.php** - Core functionality for fetching, parsing, and displaying products
|
||||
- **libs/self-update.php** - Auto-update system that checks Gitea releases API
|
||||
- **css/fw-store-embed.css** - Styling for product tiles and admin interface
|
||||
|
||||
### Key Components
|
||||
|
||||
**HTTP Request Layer** (`fwembed_make_request` in shortcode.php):
|
||||
- Centralized cURL-based HTTP client with browser-like headers to avoid 403 blocks
|
||||
- 1-hour transient caching using WordPress transients (cache key: `fwembed_{md5(url)}`)
|
||||
- SSL verification configurable via admin settings
|
||||
- Cookie handling via `/tmp/cookies.txt` for session persistence
|
||||
|
||||
**HTML Parsing** (shortcode.php):
|
||||
- Uses DOMDocument/DOMXPath to extract product data from Fourthwall HTML
|
||||
- Looks for `div[data-testid="product"]` elements
|
||||
- Extracts product tiles, images, descriptions, titles, and prices via CSS class selectors
|
||||
- `loadHTML5()` wrapper ensures proper HTML5 parsing
|
||||
|
||||
**Shortcodes**:
|
||||
- `[fourthwall]` - Displays all store products
|
||||
- `[fourthwall_single url="..." show_description="true"]` - Single product display
|
||||
- `[fourthwall_random count="5" urls="..." store_url="..."]` - Random product selection
|
||||
|
||||
**Auto-Update System** (self-update.php):
|
||||
- Hooks into WordPress plugin update transients (`site_transient_update_plugins`)
|
||||
- Fetches latest release from `https://repo.anhonesthost.net/api/v1/repos/wp-plugins/fourth-wall-embed-wp/releases/latest`
|
||||
- Provides changelog via `plugins_api` filter
|
||||
- Version placeholder `{auto_update_value_on_deploy}` is replaced during CI/CD build
|
||||
|
||||
### Settings Storage
|
||||
|
||||
WordPress options API key: `fourthwall_settings_name`
|
||||
- `fourth_url` - Default Fourthwall store URL
|
||||
- `ssl_verify` - Boolean for SSL certificate verification
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Testing the Plugin Locally
|
||||
|
||||
1. Symlink or copy to WordPress plugins directory:
|
||||
```bash
|
||||
ln -s $(pwd) /path/to/wordpress/wp-content/plugins/fourth-wall-embed-wp
|
||||
```
|
||||
|
||||
2. Activate in WordPress admin at: **Plugins > Installed Plugins**
|
||||
|
||||
3. Configure at: **Settings > Fourthwall Store Embed**
|
||||
|
||||
### Cache Management
|
||||
|
||||
Clear transient cache from admin UI or manually:
|
||||
```sql
|
||||
DELETE FROM wp_options WHERE option_name LIKE '_transient_fwembed_%';
|
||||
DELETE FROM wp_options WHERE option_name LIKE '_transient_timeout_fwembed_%';
|
||||
```
|
||||
|
||||
## CI/CD Pipeline
|
||||
|
||||
### Gitea Actions Workflows
|
||||
|
||||
**`.gitea/workflows/release.yml`** - Runs on push to `main`:
|
||||
1. Generates version tag from date/time: `YYYY.MM.DD-HHMM`
|
||||
2. Creates release notes from commits since last tag
|
||||
3. Updates version placeholder in `fw-store-embed.php`
|
||||
4. Creates ZIP archive with plugin folder structure: `fourthwall-store-embed/`
|
||||
5. Creates GitHub-style release with ZIP attachment
|
||||
|
||||
**`.gitea/workflows/update-version.yml`** - Version update automation
|
||||
|
||||
### Release Process
|
||||
|
||||
Releases are automatic on merge/push to `main`. The ZIP file structure must match WordPress conventions:
|
||||
```
|
||||
fourthwall-store-embed.zip
|
||||
└── fourthwall-store-embed/
|
||||
├── fw-store-embed.php
|
||||
├── libs/
|
||||
├── css/
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Important Implementation Notes
|
||||
|
||||
### DOMDocument HTML Parsing
|
||||
- Always use `libxml_use_internal_errors(true)` to suppress HTML5 parsing warnings
|
||||
- Clear errors with `libxml_clear_errors()` after parsing
|
||||
- Set `$dom->documentURI` for proper relative URL resolution
|
||||
|
||||
### Caching Strategy
|
||||
- All HTTP requests are cached for 1 hour (3600 seconds)
|
||||
- Cache is stored in WordPress transients, not direct database access
|
||||
- Error responses (non-200 status) are NOT cached
|
||||
- Cache keys use `md5($url)` to handle special characters
|
||||
|
||||
### Security Considerations
|
||||
- All user input sanitized via `esc_attr()`, `esc_html()`, `htmlspecialchars()`
|
||||
- Nonce verification for cache clearing: `wp_verify_nonce()`
|
||||
- Capability checks: `current_user_can('manage_options')`
|
||||
- SSL verification enabled by default (disable only for local dev)
|
||||
|
||||
### Version Management
|
||||
- Main plugin file contains placeholder: `Version: {auto_update_value_on_deploy}`
|
||||
- CI/CD replaces this during build with actual version
|
||||
- Update checker compares version strings exactly (not semantic versioning)
|
||||
|
||||
## WordPress Compatibility
|
||||
|
||||
- **Requires**: WordPress 6.0+, PHP 7.4+
|
||||
- **Tested up to**: WordPress 6.8
|
||||
- **Required PHP extensions**: cURL, libxml, DOM
|
137
README.md
137
README.md
@@ -1,12 +1,139 @@
|
||||
# fourth-wall-embed-wp
|
||||
# Fourthwall Store Embed WordPress Plugin
|
||||
|
||||
A WordPress Plugin to embed a fourth wall embed.
|
||||
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
|
||||
|
||||
* Download the latest release from [releases](https://repo.anhonesthost.net/CyberCoveLLC/fourth-wall-embed-wp/releases)
|
||||
* Upload the Plugin to WordPress
|
||||
* 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]```
|
||||
* In the WordPress Dashboard, Navigate to the Fourthwall settings page, and paste your store URL.
|
||||
|
||||
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.
|
||||
#### 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]```
|
||||
|
||||
#### 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.
|
@@ -8,17 +8,29 @@
|
||||
}
|
||||
|
||||
.product-tile {
|
||||
align-content: center;
|
||||
vertical-align: bottom;
|
||||
background: white;
|
||||
display:inline-block;
|
||||
align-content: flex-start;
|
||||
vertical-align: top;
|
||||
display:inline-flex;
|
||||
flex-direction: column;
|
||||
width:225px;
|
||||
margin:15px 15px 15px 15px;
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
.product-tile .product-link {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.product-tile img {
|
||||
max-height: 350px;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.image__badges {
|
||||
@@ -39,4 +51,56 @@
|
||||
.tile__heading {
|
||||
font-size: 0.88em; /* Reduced font size for product titles */
|
||||
font-weight: bold;
|
||||
margin: 0.5em 0;
|
||||
color: inherit;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.tile__description {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px 5px;
|
||||
color: inherit;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.tile__prices {
|
||||
margin-top: auto;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
.tile__price {
|
||||
color: inherit;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
/* 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,14 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: Fourthwall Store Embed
|
||||
* Plugin URL: https://cybercove.io/
|
||||
* Plugin URL: https://darksideofperfection.com/
|
||||
* Description: Embed Fourthwall Store in WordPress
|
||||
* Version: 1.0.0.
|
||||
* Version: {auto_update_value_on_deploy}
|
||||
* Author: Joshua Knapp
|
||||
* Text Domain: fourthwall_text_domain
|
||||
*
|
||||
*/
|
||||
|
||||
include("libs/self-update.php");
|
||||
include("libs/settings.php");
|
||||
include("libs/shortcode.php");
|
||||
function set_fw_store_embed_css() {
|
||||
@@ -16,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' );
|
||||
|
177
libs/self-update.php
Normal file
177
libs/self-update.php
Normal file
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin update functionality
|
||||
*/
|
||||
|
||||
// Don't execute directly
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function fwembed_check_for_updates() {
|
||||
// IMPORTANT: Get the main plugin file path correctly
|
||||
$main_plugin_file = dirname(dirname(__FILE__)) . '/fw-store-embed.php';
|
||||
$plugin_slug = plugin_basename($main_plugin_file);
|
||||
|
||||
// Get plugin data from main file
|
||||
if (!function_exists('get_plugin_data')) {
|
||||
require_once(ABSPATH . 'wp-admin/includes/plugin.php');
|
||||
}
|
||||
|
||||
// Get plugin data from main plugin file (not this file)
|
||||
$plugin_data = get_plugin_data($main_plugin_file);
|
||||
$current_version = !empty($plugin_data['Version']) ? $plugin_data['Version'] : '0.0.1';
|
||||
|
||||
// Debug data
|
||||
$debug_data = [
|
||||
'current_version' => $current_version,
|
||||
'main_plugin_file' => $main_plugin_file,
|
||||
'plugin_slug' => $plugin_slug,
|
||||
'time' => current_time('mysql')
|
||||
];
|
||||
|
||||
// URL to your Gitea releases API
|
||||
$gitea_api_url = 'https://repo.anhonesthost.net/api/v1/repos/wp-plugins/fourth-wall-embed-wp/releases/latest';
|
||||
|
||||
// Use cURL to fetch release data
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $gitea_api_url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept: application/json']);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, 'WordPress/Fourth-Wall-Plugin-Updater');
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response) {
|
||||
$release_info = json_decode($response);
|
||||
|
||||
// Get latest version from tag name
|
||||
$latest_version = $release_info->tag_name;
|
||||
|
||||
// Simple version comparison - we want any update to trigger if version is different
|
||||
if ($current_version != $latest_version) {
|
||||
// Get the download URL for the ZIP
|
||||
$download_url = null;
|
||||
if (isset($release_info->assets) && is_array($release_info->assets)) {
|
||||
foreach ($release_info->assets as $asset) {
|
||||
if (isset($asset->name) && strpos($asset->name, '.zip') !== false) {
|
||||
$download_url = $asset->browser_download_url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hook into WordPress update system
|
||||
add_filter('site_transient_update_plugins', function($transient) use ($plugin_slug, $current_version, $latest_version, $download_url) {
|
||||
// Initialize if needed
|
||||
if (!is_object($transient)) {
|
||||
$transient = new stdClass();
|
||||
}
|
||||
if (empty($transient->checked)) {
|
||||
$transient->checked = [];
|
||||
}
|
||||
|
||||
// Add our plugin to the checked list
|
||||
$transient->checked[$plugin_slug] = $current_version;
|
||||
|
||||
if (empty($transient->response)) {
|
||||
$transient->response = [];
|
||||
}
|
||||
|
||||
// Add to the update list
|
||||
$transient->response[$plugin_slug] = (object) [
|
||||
'id' => 'fourthwall-store-embed',
|
||||
'slug' => 'fourthwall-store-embed',
|
||||
'plugin' => $plugin_slug,
|
||||
'new_version' => $latest_version,
|
||||
'url' => 'https://repo.anhonesthost.net/wp-plugins/fourth-wall-embed-wp',
|
||||
'package' => $download_url,
|
||||
'icons' => [],
|
||||
'banners' => [],
|
||||
'tested' => '6.8',
|
||||
'requires' => '6.2',
|
||||
'compatibility' => new stdClass(),
|
||||
];
|
||||
|
||||
return $transient;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
add_action('admin_init', 'fwembed_check_for_updates');
|
||||
|
||||
|
||||
// Add plugin information for the details popup/changelog
|
||||
add_filter('plugins_api', function($result, $action, $args) {
|
||||
// Only handle requests for our plugin
|
||||
$plugin_slug = 'fourthwall-store-embed'; // The slug is directory name
|
||||
|
||||
if ($action !== 'plugin_information' || !isset($args->slug) || $args->slug !== $plugin_slug) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Get latest release info from Gitea API
|
||||
$gitea_api_url = 'https://repo.anhonesthost.net/api/v1/repos/wp-plugins/fourth-wall-embed-wp/releases/latest';
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $gitea_api_url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept: application/json']);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, 'WordPress/Fourth-Wall-Plugin-Updater');
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if (!$response) {
|
||||
return $result; // Keep default result if API call fails
|
||||
}
|
||||
|
||||
$release_info = json_decode($response);
|
||||
|
||||
// Build plugin info object
|
||||
$plugin_info = new stdClass();
|
||||
$plugin_info->name = 'Fourthwall Store Embed';
|
||||
$plugin_info->slug = $plugin_slug;
|
||||
$plugin_info->version = $release_info->tag_name;
|
||||
$plugin_info->author = '<a href="https://cybercove.io/">Joshua Knapp</a>';
|
||||
$plugin_info->homepage = 'https://repo.anhonesthost.net/wp-plugins/fourth-wall-embed-wp';
|
||||
$plugin_info->requires = '5.0';
|
||||
$plugin_info->tested = '6.8';
|
||||
$plugin_info->downloaded = 10;
|
||||
$plugin_info->last_updated = $release_info->published_at;
|
||||
|
||||
// Format the release notes
|
||||
$changelog = !empty($release_info->body) ? $release_info->body : 'No changelog provided for this release.';
|
||||
|
||||
// Handle empty changelog
|
||||
if (empty(trim($changelog))) {
|
||||
$changelog = "Version " . $release_info->tag_name . "\n\n" .
|
||||
"Released on " . date('F j, Y', strtotime($release_info->published_at)) . "\n\n" .
|
||||
"* Updated plugin files";
|
||||
}
|
||||
|
||||
// Format sections for display
|
||||
$plugin_info->sections = [
|
||||
'description' => '<p>Embed Fourthwall Store in WordPress.</p>',
|
||||
'changelog' => '<pre>' . esc_html($changelog) . '</pre>',
|
||||
'installation' => '<p>Upload the plugin to your WordPress site and activate it.</p>'
|
||||
];
|
||||
|
||||
// Download link
|
||||
$download_url = null;
|
||||
if (isset($release_info->assets) && is_array($release_info->assets)) {
|
||||
foreach ($release_info->assets as $asset) {
|
||||
if (isset($asset->name) && strpos($asset->name, '.zip') !== false) {
|
||||
$download_url = $asset->browser_download_url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$plugin_info->download_link = $download_url;
|
||||
|
||||
return $plugin_info;
|
||||
}, 10, 3);
|
@@ -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 '<div class="notice notice-success"><p>' . __('Cache cleared successfully!', 'fourthwall_text_domain') . '</p></div>';
|
||||
}
|
||||
|
||||
// Admin Page Layout
|
||||
echo '<div class="wrap">' . "\n";
|
||||
echo ' <h1>' . get_admin_page_title() . '</h1>' . "\n";
|
||||
@@ -64,7 +78,24 @@ class fourthwall_settings {
|
||||
submit_button();
|
||||
|
||||
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";
|
||||
|
||||
}
|
||||
@@ -83,6 +114,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;
|
||||
|
@@ -1,102 +1,358 @@
|
||||
<?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) {
|
||||
if ($url === null) {
|
||||
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>';
|
||||
throw new ValueError("Missing URL");
|
||||
}
|
||||
}
|
||||
libxml_clear_errors();
|
||||
return $html;
|
||||
|
||||
$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 '<!DOCTYPE html><html><body>' . $html . '</body></html>';
|
||||
return '<!DOCTYPE html><html><body>' . $html . '</body></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 = '<div class="fw-store-parent">' . PHP_EOL . $store_html . PHP_EOL . '</div>';
|
||||
return $store_render;
|
||||
$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 = '<div class="fw-store-parent">' . PHP_EOL . $store_html . PHP_EOL . '</div>';
|
||||
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_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>';
|
||||
}
|
||||
|
Reference in New Issue
Block a user