Skip to content

Commit b9084d6

Browse files
feat: initial LeadCapture Form plugin v1.0.0
- Shortcode [leadcapture_form] with embed and popup modes - Gutenberg block with live preview - Elementor widget with style controls - Settings Hub integration with fallback admin page - GitHub auto-updater via silverassist/wp-github-updater - Lazy-loading pixel script (vanilla JS, no jQuery) - PHPCS (WPCS 3.3.0) and PHPStan (level 8) clean - CI/CD workflows (quality checks + release) - Build and version management scripts
0 parents  commit b9084d6

26 files changed

+4015
-0
lines changed

.github/copilot-instructions.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# LeadCapture Form — Project Context
2+
3+
WordPress plugin for embedding LeadCapture.io forms via shortcode, Gutenberg block, or Elementor widget with embed and popup mode support.
4+
5+
## Plugin Info
6+
7+
| Key | Value |
8+
|------------------|--------------------------------|
9+
| Namespace | `LeadCaptureForm` |
10+
| Text Domain | `leadcapture-form` |
11+
| Version | 1.0.0 |
12+
| Requires PHP | 8.2 |
13+
| License | Polyform Noncommercial 1.0.0 |
14+
| GitHub Repo | `SilverAssist/leadcapture-form`|
15+
16+
## Differences from Global Standards
17+
18+
- **Double quotes** everywhere (PHP and JS) — not single quotes
19+
- **Singleton pattern** (`get_instance()`) — not LoadableInterface
20+
- **No activation/deactivation hooks** — plugin doesn't modify WP internals
21+
- PSR-4 autoloading via `require_once` in `load_dependencies()`, not a DI container
22+
- **Vanilla JavaScript** — no jQuery dependency
23+
24+
## Architecture
25+
26+
```
27+
leadcapture-form.php # Entry point (Singleton)
28+
includes/
29+
├── LeadCaptureFormBlock.php # Gutenberg block handler
30+
├── LeadCaptureFormUpdater.php # GitHub updater (extends silverassist/wp-github-updater)
31+
├── LeadCaptureFormAdmin.php # Admin interface (Settings → LeadCapture)
32+
└── elementor/
33+
├── WidgetsLoader.php # Conditional loader (only when Elementor active)
34+
└── widgets/
35+
└── LeadCaptureFormWidget.php # Elementor widget
36+
blocks/leadcapture-form/ # Gutenberg block assets (block.json, block.js, editor.css)
37+
assets/css/ # leadcapture-form.css, admin-settings.css
38+
assets/js/ # leadcapture-form.js (frontend, vanilla JS)
39+
```
40+
41+
### Namespaces
42+
43+
- `LeadCaptureForm` — main plugin classes
44+
- `LeadCaptureForm\Block` — Gutenberg block
45+
- `LeadCaptureForm\Elementor` — Elementor loader
46+
- `LeadCaptureForm\Elementor\Widgets` — Elementor widgets
47+
48+
### Key Differences from LeadGen App Form
49+
50+
- **Single form-token** (not desktop-id/mobile-id) — LeadCapture.io handles responsiveness internally
51+
- **Embed & popup modes**`mode="embed"` for inline, `mode="popup"` for click-triggered popup
52+
- **LeadCapture.io pixel script** — loads `https://api.useleadbot.com/lead-bots/get-pixel-script.js`
53+
- **`window.form_token`** — set before script loads (not per-form custom elements)
54+
- **Embed container**`<div class="leadforms-embd-form"></div>` populated by pixel script
55+
- **Popup trigger** — CSS class `leadforms-trigger-XX` on trigger elements
56+
57+
### Form Integration Methods
58+
59+
```php
60+
// Shortcode — embed mode
61+
[leadcapture_form form-token="GLFT-XXXXX" mode="embed" height="600px"]
62+
63+
// Shortcode — popup mode
64+
[leadcapture_form form-token="GLFT-XXXXX" mode="popup" trigger-class="leadforms-trigger-01"]
65+
66+
// Gutenberg Block — search "LeadCapture Form" in block inserter
67+
// Elementor Widget — drag from "LeadCapture Forms" category
68+
```
69+
70+
### Form Loading Flow
71+
72+
1. PHP renders placeholder with pulse animation
73+
2. JS waits for user interaction (focus/mousemove/scroll/touchstart)
74+
3. Sets `window.form_token` from data attribute
75+
4. Loads pixel script from `api.useleadbot.com` (singleton — only loaded once)
76+
5. Script populates `<div class="leadforms-embd-form">` for embed mode
77+
6. For popup mode, trigger elements use `leadforms-trigger-XX` CSS class
78+
79+
### Update System
80+
81+
Uses `silverassist/wp-github-updater` package. `LeadCaptureFormUpdater` extends `GitHubUpdater` with plugin-specific config (asset pattern `leadcapture-form-v{version}.zip`, 12h cache, AJAX action `leadcapture_check_version`). Updates show in standard WP admin.
82+
83+
### Elementor Integration
84+
85+
- Conditional loading: `\did_action('elementor/loaded')`
86+
- Widget category: `leadcapture-forms`
87+
- Hooks: `elementor/widgets/register`, `elementor/elements/categories_registered`
88+
- Renders via same `render_shortcode()` method as the shortcode
89+
90+
## Quick Reference
91+
92+
| File | Role |
93+
|------|------|
94+
| `leadcapture-form.php` | Main plugin file (Singleton) |
95+
| `includes/LeadCaptureFormBlock.php` | Gutenberg block handler |
96+
| `includes/LeadCaptureFormUpdater.php` | GitHub updater |
97+
| `includes/LeadCaptureFormAdmin.php` | Admin settings page |
98+
| `includes/elementor/WidgetsLoader.php` | Elementor widgets manager |
99+
| `includes/elementor/widgets/LeadCaptureFormWidget.php` | LeadCapture Form widget |
100+
| `assets/js/leadcapture-form.js` | Frontend form loading (Vanilla JS, lazy load) |
101+
| `assets/css/leadcapture-form.css` | Main styles + animations |
102+
| `assets/css/admin-settings.css` | Admin page card-based UI |
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
name: Plugin Quality Checks
2+
permissions:
3+
contents: read
4+
5+
on:
6+
push:
7+
branches: [main, develop]
8+
pull_request:
9+
branches: [main]
10+
11+
jobs:
12+
quality-checks:
13+
runs-on: ubuntu-latest
14+
15+
strategy:
16+
matrix:
17+
php-version: [8.2, 8.3, 8.4]
18+
wordpress-version: [6.5, 6.6, latest]
19+
20+
steps:
21+
- name: Checkout code
22+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
23+
24+
- name: Setup PHP
25+
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # v2.36.0
26+
with:
27+
php-version: ${{ matrix.php-version }}
28+
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite
29+
coverage: none
30+
31+
- name: Validate composer.json
32+
run: composer validate --strict
33+
34+
- name: Install Composer dependencies
35+
run: composer install --prefer-dist --no-progress --no-suggest
36+
37+
- name: Configure WordPress Coding Standards
38+
run: vendor/bin/phpcs --config-set installed_paths vendor/wp-coding-standards/wpcs
39+
40+
- name: Run PHP CodeSniffer
41+
run: |
42+
# Run basic syntax validation
43+
echo "🔍 Running PHP syntax validation..."
44+
find . -name "*.php" -not -path "./vendor/*" -exec php -l {} \;
45+
echo "✅ PHP syntax validation completed"
46+
47+
security-check:
48+
runs-on: ubuntu-latest
49+
50+
steps:
51+
- name: Checkout code
52+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
53+
54+
- name: Setup PHP
55+
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # v2.36.0
56+
with:
57+
php-version: 8.2
58+
59+
- name: Install dependencies
60+
run: composer install --prefer-dist --no-progress
61+
62+
- name: Run security check
63+
run: |
64+
# Check for common security issues (only in PHP files, excluding this workflow)
65+
echo "🔍 Checking for eval() usage in PHP code..."
66+
EVAL_FILES=$(find . -name "*.php" -not -path "./vendor/*" -exec grep -l "eval(" {} \; 2>/dev/null || true)
67+
if [ -n "$EVAL_FILES" ]; then
68+
echo "❌ eval() usage found in PHP files - security risk!"
69+
echo "$EVAL_FILES"
70+
exit 1
71+
else
72+
echo "✅ No eval() found in PHP code"
73+
fi
74+
75+
echo "🔍 Checking for direct \$_GET usage in PHP code..."
76+
GET_FILES=$(find . -name "*.php" -not -path "./vendor/*" -exec grep -l "\$_GET\[" {} \; 2>/dev/null || true)
77+
if [ -n "$GET_FILES" ]; then
78+
echo "⚠️ Direct \$_GET usage found - consider using sanitize_text_field()"
79+
echo "$GET_FILES"
80+
else
81+
echo "✅ No direct \$_GET usage in PHP code"
82+
fi
83+
84+
echo "🔍 Checking for direct \$_POST usage in PHP code..."
85+
POST_FILES=$(find . -name "*.php" -not -path "./vendor/*" -exec grep -l "\$_POST\[" {} \; 2>/dev/null || true)
86+
if [ -n "$POST_FILES" ]; then
87+
echo "⚠️ Direct \$_POST usage found - consider using sanitize_text_field()"
88+
echo "$POST_FILES"
89+
else
90+
echo "✅ No direct \$_POST usage in PHP code"
91+
fi
92+
93+
echo "🔍 Checking for remote file_get_contents usage in PHP code..."
94+
HTTP_FILES=$(find . -name "*.php" -not -path "./vendor/*" -exec grep -l "file_get_contents.*http" {} \; 2>/dev/null || true)
95+
if [ -n "$HTTP_FILES" ]; then
96+
echo "❌ Remote file_get_contents found - potential SSRF risk!"
97+
echo "$HTTP_FILES"
98+
exit 1
99+
else
100+
echo "✅ No remote file_get_contents found in PHP code"
101+
fi
102+
103+
compatibility-check:
104+
runs-on: ubuntu-latest
105+
106+
steps:
107+
- name: Checkout code
108+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
109+
110+
- name: Setup PHP
111+
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # v2.36.0
112+
with:
113+
php-version: 8.2
114+
115+
- name: Check PHP 8.2+ compatibility
116+
run: |
117+
# Check for PHP 8.2+ features usage
118+
php -r "
119+
if (version_compare(PHP_VERSION, '8.2.0', '<')) {
120+
echo 'PHP 8.2+ required';
121+
exit(1);
122+
}
123+
echo '✅ PHP version compatible';
124+
"
125+
126+
- name: WordPress compatibility check
127+
run: |
128+
# Basic WordPress function usage validation (only in PHP files)
129+
find . -name "*.php" -not -path "./vendor/*" -not -path "./.github/*" -not -path "./node_modules/*" -exec grep -l "add_action\|add_filter\|wp_enqueue" {} \; && echo "✅ WordPress functions found" || echo "⚠️ No WordPress functions detected"

.github/workflows/release.yml

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
workflow_dispatch:
8+
inputs:
9+
version:
10+
description: 'Version to release (e.g. 1.2.0). Leave empty to use plugin file version.'
11+
required: false
12+
13+
permissions:
14+
contents: write
15+
16+
jobs:
17+
release:
18+
name: Build & Release
19+
runs-on: ubuntu-latest
20+
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
24+
25+
- name: Setup PHP
26+
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # v2.36.0
27+
with:
28+
php-version: '8.2'
29+
tools: composer
30+
31+
- name: Detect version
32+
id: version
33+
run: |
34+
if [[ "${{ github.event_name }}" == "push" ]]; then
35+
VERSION="${GITHUB_REF_NAME#v}"
36+
elif [[ -n "${{ github.event.inputs.version }}" ]]; then
37+
VERSION="${{ github.event.inputs.version }}"
38+
else
39+
VERSION=$(grep -o 'Version: [0-9]\+\.[0-9]\+\.[0-9]\+' *.php 2>/dev/null | head -1 | cut -d' ' -f2)
40+
fi
41+
echo "version=$VERSION" >> $GITHUB_OUTPUT
42+
echo "🏷️ Version: $VERSION"
43+
44+
- name: Install dependencies
45+
run: composer install --no-interaction
46+
47+
- name: Update version
48+
run: |
49+
VERSION="${{ steps.version.outputs.version }}"
50+
if [ -f "scripts/update-version-simple.sh" ]; then
51+
chmod +x scripts/update-version-simple.sh
52+
./scripts/update-version-simple.sh "$VERSION" --no-confirm --force
53+
elif [ -f "scripts/update-version.sh" ]; then
54+
chmod +x scripts/update-version.sh
55+
./scripts/update-version.sh "$VERSION" --no-confirm --force
56+
fi
57+
58+
- name: Code quality
59+
run: |
60+
HAS_CHECKS=false
61+
if [ -f phpcs.xml ] || [ -f .phpcs.xml.dist ] || [ -f phpcs.xml.dist ]; then
62+
echo "▶ PHPCS"
63+
vendor/bin/phpcs --runtime-set ignore_warnings_on_exit 1
64+
HAS_CHECKS=true
65+
fi
66+
if [ -f phpstan.neon ] || [ -f phpstan.neon.dist ]; then
67+
echo "▶ PHPStan"
68+
vendor/bin/phpstan analyse --no-progress
69+
HAS_CHECKS=true
70+
fi
71+
if [ "$HAS_CHECKS" = false ]; then
72+
echo "ℹ️ No quality tool configs found — skipping"
73+
fi
74+
75+
- name: Build release
76+
id: build
77+
run: |
78+
chmod +x scripts/build-release.sh
79+
./scripts/build-release.sh "${{ steps.version.outputs.version }}"
80+
81+
- name: Create GitHub Release
82+
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
83+
with:
84+
tag_name: v${{ steps.version.outputs.version }}
85+
name: v${{ steps.version.outputs.version }}
86+
files: |
87+
build/*.zip
88+
build/*.md5
89+
build/*.sha256
90+
generate_release_notes: true
91+
env:
92+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Composer
2+
/vendor/
3+
composer.lock
4+
5+
# Node modules
6+
/node_modules/
7+
8+
# Development tools
9+
*.tmp
10+
*.log
11+
12+
# OS generated files
13+
.DS_Store
14+
.DS_Store?
15+
._*
16+
.Spotlight-V100
17+
.Trashes
18+
ehthumbs.db
19+
Thumbs.db
20+
21+
# IDE files
22+
.vscode/
23+
.idea/
24+
*.swp
25+
*.swo
26+
27+
# WordPress
28+
wp-config.php
29+
wp-content/uploads/
30+
wp-content/cache/
31+
wp-content/backup-db/
32+
wp-content/advanced-cache.php
33+
wp-content/wp-cache-config.php
34+
35+
# Package builds
36+
/dist/
37+
/build/
38+
39+
# Temporary files
40+
temp-package/
41+
temp-package.zip
42+
43+
# Generated files (created by GitHub Actions)
44+
RELEASE-NOTES.md

0 commit comments

Comments
 (0)