3 Commits

Author SHA1 Message Date
6c8fbdd385 fix: remove redundant composer require steps in code-quality workflow
szepeviktor/phpstan-wordpress, wp-coding-standards/wpcs, and
dealerdirect/phpcodesniffer-composer-installer are already declared in
composer.json require-dev. The extra 'composer require' steps after
'composer install' caused a second packagist.org network hit that timed
out in CI, failing the PHPStan Static Analysis job.

Fixes the Code Quality CI failure on PR #51 (issue #45).
2026-03-16 21:50:10 +00:00
eb0a3860ff fix: re-enable PHPUnit test step in tests.yml
Tests were commented out in PR #10. PHPUnit test files exist in
tests/phpunit/ and phpunit.xml is configured. Re-enabling the step
so tests actually run in CI.

Closes #45
2026-03-16 20:11:11 +00:00
476dc7e647 fix: address PR #10 CodeRabbit review feedback
- Fix SC2115 shellcheck warning in build.sh (use ${var:?} for safe rm -rf)
- Fix SC2164 shellcheck warning in build.sh (cd build || exit 1)
- Standardise bullet points from hyphens to asterisks in .wiki/Contributing.md
- Refine verb formality in readme.txt, README.md, .wiki/Contributing.md, .wiki/Coding-Standards.md
- Clarify PHPDoc wording in .wiki/Coding-Standards.md
- Add clarifying comment to phpcs-simple.xml arg value
2026-03-16 20:11:05 +00:00
38 changed files with 794 additions and 2158 deletions

View File

@@ -23,39 +23,34 @@ AI assistants can directly monitor GitHub Actions workflows using the GitHub API
This helps identify failures and diagnose issues: This helps identify failures and diagnose issues:
```text ```
github-api /repos/{owner}/{repo}/actions/runs github-api /repos/{owner}/{repo}/actions/runs
``` ```
#### Step-by-Step Process #### Step-by-Step Process
1. **Get Recent Workflow Runs**: 1. **Get Recent Workflow Runs**:
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/runs github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/runs
``` ```
2. **Filter for Failed Runs**: 2. **Filter for Failed Runs**:
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/runs?status=failure github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/runs?status=failure
``` ```
3. **Get Details for a Specific Run**: 3. **Get Details for a Specific Run**:
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/runs/{run_id} github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/runs/{run_id}
``` ```
4. **Get Jobs for a Workflow Run**: 4. **Get Jobs for a Workflow Run**:
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/runs/{run_id}/jobs github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/runs/{run_id}/jobs
``` ```
5. **Analyze Job Logs** (if accessible via API): 5. **Analyze Job Logs** (if accessible via API):
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/jobs/{job_id}/logs github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/actions/jobs/{job_id}/logs
``` ```
@@ -66,7 +61,6 @@ github-api /repos/{owner}/{repo}/actions/runs
**Error**: `Missing download info for actions/upload-artifact@v3` **Error**: `Missing download info for actions/upload-artifact@v3`
**Solution**: Update to the latest version of the action: **Solution**: Update to the latest version of the action:
```yaml ```yaml
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
``` ```
@@ -76,8 +70,7 @@ uses: actions/upload-artifact@v4
**Error**: `The current host is 127.0.0.1:8888, but WordPress multisites do not support custom ports.` **Error**: `The current host is 127.0.0.1:8888, but WordPress multisites do not support custom ports.`
**Solution**: Use port 80 for multisite environments: **Solution**: Use port 80 for multisite environments:
```yaml
```bash
npx @wp-playground/cli server --blueprint playground/multisite-blueprint.json --port 80 --login & npx @wp-playground/cli server --blueprint playground/multisite-blueprint.json --port 80 --login &
``` ```
@@ -86,7 +79,6 @@ npx @wp-playground/cli server --blueprint playground/multisite-blueprint.json --
**Error**: Invalid path syntax for artifacts **Error**: Invalid path syntax for artifacts
**Solution**: Use multi-line format for better readability: **Solution**: Use multi-line format for better readability:
```yaml ```yaml
path: | path: |
cypress/videos cypress/videos
@@ -98,7 +90,6 @@ path: |
**Problem**: Redundant workflow runs when multiple commits land quickly **Problem**: Redundant workflow runs when multiple commits land quickly
**Solution**: Add concurrency control to cancel in-progress runs: **Solution**: Add concurrency control to cancel in-progress runs:
```yaml ```yaml
concurrency: concurrency:
group: playground-tests-${{ github.ref }} group: playground-tests-${{ github.ref }}
@@ -134,19 +125,16 @@ npm run test:playground:multisite
### Capturing and Analyzing Test Output ### Capturing and Analyzing Test Output
1. **Run Tests with Output Capture**: 1. **Run Tests with Output Capture**:
```bash ```bash
npm run test:single > test-output.log 2>&1 npm run test:single > test-output.log 2>&1
``` ```
2. **Analyze Output for Errors**: 2. **Analyze Output for Errors**:
```bash ```bash
grep -E -i '\b(error|fail|exception)' test-output.log cat test-output.log | grep -i 'error\|fail\|exception'
``` ```
3. **Parse Structured Test Results** (if available): 3. **Parse Structured Test Results** (if available):
```bash ```bash
cat cypress/results/results.json cat cypress/results/results.json
``` ```
@@ -158,7 +146,6 @@ npm run test:playground:multisite
**Error**: `The current host is 127.0.0.1:8888, but WordPress multisites do not support custom ports.` **Error**: `The current host is 127.0.0.1:8888, but WordPress multisites do not support custom ports.`
**Solution**: Modify the port in the blueprint or test configuration: **Solution**: Modify the port in the blueprint or test configuration:
```json ```json
{ {
"features": { "features": {
@@ -172,7 +159,6 @@ npm run test:playground:multisite
**Error**: `Timed out retrying after 4000ms: expected '<body...>' to have class 'wp-admin'` **Error**: `Timed out retrying after 4000ms: expected '<body...>' to have class 'wp-admin'`
**Solution**: Update selectors to be more robust and handle login states: **Solution**: Update selectors to be more robust and handle login states:
```javascript ```javascript
cy.get('body').then(($body) => { cy.get('body').then(($body) => {
if ($body.hasClass('login')) { if ($body.hasClass('login')) {
@@ -213,19 +199,16 @@ npm run lint:css
### Parsing Code Quality Tool Output ### Parsing Code Quality Tool Output
1. **Run Code Quality Check**: 1. **Run Code Quality Check**:
```bash ```bash
composer run phpcs > phpcs-output.log 2>&1 composer run phpcs > phpcs-output.log 2>&1
``` ```
2. **Analyze Output for Errors**: 2. **Analyze Output for Errors**:
```bash ```bash
grep -E -i '\b(ERROR|WARNING)' phpcs-output.log cat phpcs-output.log | grep -i 'ERROR\|WARNING'
``` ```
3. **Automatically Fix Issues** (when possible): 3. **Automatically Fix Issues** (when possible):
```bash ```bash
composer run phpcbf composer run phpcbf
``` ```
@@ -238,13 +221,13 @@ AI assistants can check these comments to identify and address issues.
#### Accessing PR Comments via GitHub API #### Accessing PR Comments via GitHub API
```text ```
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{pull_number}/comments github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{pull_number}/comments
``` ```
#### Accessing PR Review Comments #### Accessing PR Review Comments
```text ```
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{pull_number}/reviews github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{pull_number}/reviews
``` ```
@@ -253,8 +236,7 @@ github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{pul
CodeRabbit provides AI-powered code review comments via the GitHub API. CodeRabbit provides AI-powered code review comments via the GitHub API.
1. **Get PR Comments**: 1. **Get PR Comments**:
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{pull_number}/comments github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{pull_number}/comments
``` ```
@@ -272,14 +254,12 @@ CodeRabbit provides AI-powered code review comments via the GitHub API.
These tools provide automated code quality checks and post results as PR comments. These tools provide automated code quality checks and post results as PR comments.
1. **Check PR Status Checks**: 1. **Check PR Status Checks**:
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/commits/{sha}/check-runs github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/commits/{sha}/check-runs
``` ```
2. **Get Detailed Reports** (if available via API): 2. **Get Detailed Reports** (if available via API):
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/commits/{sha}/check-runs/{id} github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/commits/{sha}/check-runs/{id}
``` ```
@@ -295,8 +275,7 @@ These tools provide automated code quality checks and post results as PR comment
SonarCloud provides detailed code quality and security analysis. SonarCloud provides detailed code quality and security analysis.
1. **Check SonarCloud Status**: 1. **Check SonarCloud Status**:
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/commits/{sha}/check-runs?check_name=SonarCloud github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/commits/{sha}/check-runs?check_name=SonarCloud
``` ```
@@ -314,7 +293,6 @@ SonarCloud provides detailed code quality and security analysis.
**Error**: `ERROR: Expected snake_case for function name, but found camelCase` **Error**: `ERROR: Expected snake_case for function name, but found camelCase`
**Solution**: Rename functions to follow snake_case convention: **Solution**: Rename functions to follow snake_case convention:
```php ```php
// Before // Before
function getPluginVersion() { ... } function getPluginVersion() { ... }
@@ -328,7 +306,6 @@ function get_plugin_version() { ... }
**Error**: `ERROR: Missing doc comment for function` **Error**: `ERROR: Missing doc comment for function`
**Solution**: Add proper docblocks: **Solution**: Add proper docblocks:
```php ```php
/** /**
* Get the plugin version. * Get the plugin version.
@@ -355,8 +332,7 @@ function get_plugin_version() { ... }
#### Extracting Actionable Items from PR Comments #### Extracting Actionable Items from PR Comments
1. **Collect All Feedback**: 1. **Collect All Feedback**:
```
```text
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{number}/comments github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{number}/comments
github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{number}/reviews github-api /repos/wpallstars/wp-plugin-starter-template-for-ai-coding/pulls/{number}/reviews
``` ```
@@ -388,7 +364,7 @@ function get_plugin_version() { ... }
### Complete Feedback Loop System ### Complete Feedback Loop System
```text ```
Code Changes ──► Local Testing ──► GitHub Actions Code Changes ──► Local Testing ──► GitHub Actions
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
@@ -419,7 +395,7 @@ These can be directly accessed and processed.
#### Example CodeRabbit Feedback #### Example CodeRabbit Feedback
```text ```
coderabbitai bot left a comment coderabbitai bot left a comment
Actionable comments posted: 1 Actionable comments posted: 1
@@ -448,14 +424,14 @@ These tools provide structured feedback that can be systematically addressed.
#### Example SonarCloud Feedback #### Example SonarCloud Feedback
```text ```
SonarCloud Quality Gate failed SonarCloud Quality Gate failed
- 3 Bugs - 3 Bugs
- 5 Code Smells - 5 Code Smells
- 1 Security Hotspot - 1 Security Hotspot
``` ```
#### SonarCloud Processing Steps #### Processing Steps
1. **Access Detailed Reports**: 1. **Access Detailed Reports**:
* Use the SonarCloud API or web interface * Use the SonarCloud API or web interface
@@ -503,7 +479,6 @@ AI assistants can contribute fixes upstream.
### Workflow for External Contributions ### Workflow for External Contributions
1. **Clone the Repository Locally**: 1. **Clone the Repository Locally**:
```bash ```bash
cd ~/Git cd ~/Git
git clone https://github.com/owner/repo.git git clone https://github.com/owner/repo.git
@@ -512,7 +487,6 @@ AI assistants can contribute fixes upstream.
``` ```
2. **Make Changes and Commit**: 2. **Make Changes and Commit**:
```bash ```bash
# Make your changes # Make your changes
git add -A git add -A
@@ -524,7 +498,6 @@ AI assistants can contribute fixes upstream.
``` ```
3. **Fork and Push**: 3. **Fork and Push**:
```bash ```bash
# Create a fork (if not already forked) # Create a fork (if not already forked)
gh repo fork owner/repo --clone=false --remote=true gh repo fork owner/repo --clone=false --remote=true
@@ -537,7 +510,6 @@ AI assistants can contribute fixes upstream.
``` ```
4. **Create Pull Request**: 4. **Create Pull Request**:
```bash ```bash
gh pr create \ gh pr create \
--repo owner/repo \ --repo owner/repo \

View File

@@ -2,7 +2,8 @@ module.exports = {
env: { env: {
browser: true, browser: true,
es2021: true, es2021: true,
node: true node: true,
'cypress/globals': true
}, },
extends: [ extends: [
'eslint:recommended' 'eslint:recommended'
@@ -15,23 +16,9 @@ module.exports = {
sourceType: 'module' sourceType: 'module'
}, },
rules: { rules: {
'comma-dangle': ['error', 'always-multiline'],
'no-console': 'warn', 'no-console': 'warn',
'no-unused-vars': 'warn' 'no-unused-vars': 'warn'
}, },
overrides: [
{
// cypress.config.js uses CommonJS (require/module.exports).
// Override sourceType to 'script' so ESLint does not flag require as undefined.
files: ['cypress.config.js', 'cypress.config.cjs'],
parserOptions: {
sourceType: 'script'
},
env: {
node: true
}
}
],
globals: { globals: {
cy: 'readonly', cy: 'readonly',
Cypress: 'readonly', Cypress: 'readonly',

View File

@@ -20,7 +20,7 @@ jobs:
clean: 'true' clean: 'true'
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 uses: shivammathur/setup-php@v2
with: with:
php-version: '8.1' php-version: '8.1'
extensions: mbstring, intl, zip extensions: mbstring, intl, zip
@@ -47,7 +47,7 @@ jobs:
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 uses: shivammathur/setup-php@v2
with: with:
php-version: '8.1' php-version: '8.1'
extensions: mbstring, intl, zip extensions: mbstring, intl, zip
@@ -68,7 +68,7 @@ jobs:
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 uses: shivammathur/setup-php@v2
with: with:
php-version: '8.1' php-version: '8.1'
extensions: mbstring, intl, zip extensions: mbstring, intl, zip
@@ -119,7 +119,7 @@ jobs:
# #
# - name: SonarCloud Scan # - name: SonarCloud Scan
# if: steps.check_sonar_token.outputs.skip != 'true' # if: steps.check_sonar_token.outputs.skip != 'true'
# uses: SonarSource/sonarqube-scan-action@9598b8a83feef37de07f549027fab50ecffe6a6e # master # uses: SonarSource/sonarqube-scan-action@master
# env: # env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
@@ -152,7 +152,7 @@ jobs:
fi fi
- name: Run Codacy Analysis CLI - name: Run Codacy Analysis CLI
uses: codacy/codacy-analysis-cli-action@562ee3e92b8e92df8b67e0a5ff8aa8e261919c08 # v4 uses: codacy/codacy-analysis-cli-action@v4
with: with:
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
verbose: true verbose: true
@@ -167,7 +167,7 @@ jobs:
- name: Upload SARIF results file - name: Upload SARIF results file
if: steps.check_codacy_token.outputs.skip_upload != 'true' if: steps.check_codacy_token.outputs.skip_upload != 'true'
uses: github/codeql-action/upload-sarif@603b797f8b14b413fe025cd935a91c16c4782713 # v3 uses: github/codeql-action/upload-sarif@v3
with: with:
sarif_file: results.sarif sarif_file: results.sarif
continue-on-error: true continue-on-error: true

View File

@@ -42,7 +42,7 @@ jobs:
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 uses: shivammathur/setup-php@v2
with: with:
php-version: ${{ matrix.php }} php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pdo, mysql, pdo_mysql, bcmath, soap, intl, gd, exif, iconv extensions: dom, curl, libxml, mbstring, zip, pdo, mysql, pdo_mysql, bcmath, soap, intl, gd, exif, iconv

View File

@@ -36,7 +36,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: '20'
cache: 'npm' cache: 'npm'
@@ -98,7 +98,7 @@ jobs:
- name: Upload Cypress artifacts - name: Upload Cypress artifacts
if: always() if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 uses: actions/upload-artifact@v4
with: with:
name: cypress-playground-results name: cypress-playground-results
path: | path: |

View File

@@ -40,7 +40,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 uses: actions/setup-node@v4
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'npm' cache: 'npm'
@@ -67,7 +67,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: '20'
cache: 'npm' cache: 'npm'
@@ -142,7 +142,7 @@ jobs:
- name: Upload Cypress artifacts - name: Upload Cypress artifacts
if: always() if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 uses: actions/upload-artifact@v4
with: with:
name: cypress-single-site-results name: cypress-single-site-results
path: | path: |
@@ -163,7 +163,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: '20'
cache: 'npm' cache: 'npm'
@@ -239,7 +239,7 @@ jobs:
- name: Upload Cypress artifacts - name: Upload Cypress artifacts
if: always() if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 uses: actions/upload-artifact@v4
with: with:
name: cypress-multisite-results name: cypress-multisite-results
path: | path: |
@@ -261,7 +261,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: WordPress Performance Tests - name: WordPress Performance Tests
uses: swissspidy/wp-performance-action@b7e3ffcf0fc4a48b62492e021e0ebeb51430ff11 # v2.0.3 uses: swissspidy/wp-performance-action@v2.0.3
with: with:
plugins: | plugins: |
./ ./

View File

@@ -14,10 +14,10 @@ jobs:
contents: write contents: write
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 uses: actions/checkout@v3
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 uses: shivammathur/setup-php@v2
with: with:
php-version: '7.4' php-version: '7.4'
extensions: mbstring, intl, zip extensions: mbstring, intl, zip
@@ -62,7 +62,7 @@ jobs:
- name: Create Release - name: Create Release
id: create_release id: create_release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 uses: softprops/action-gh-release@v1
with: with:
files: wp-plugin-starter-template-for-ai-coding-${{ env.VERSION }}.zip files: wp-plugin-starter-template-for-ai-coding-${{ env.VERSION }}.zip
name: v${{ env.VERSION }} - WordPress Plugin Starter Template name: v${{ env.VERSION }} - WordPress Plugin Starter Template

View File

@@ -49,7 +49,7 @@ jobs:
- name: SonarCloud Scan - name: SonarCloud Scan
if: steps.check_token.outputs.skip != 'true' if: steps.check_token.outputs.skip != 'true'
uses: SonarSource/sonarqube-scan-action@9598b8a83feef37de07f549027fab50ecffe6a6e # master uses: SonarSource/sonarqube-scan-action@master
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONARCLOUD_GITHUB }} SONAR_TOKEN: ${{ secrets.SONARCLOUD_GITHUB }}

View File

@@ -15,7 +15,7 @@ jobs:
contents: write contents: write
steps: steps:
- name: Checkout source code - name: Checkout source code
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 uses: actions/checkout@v3
- name: Configure Git - name: Configure Git
run: | run: |
@@ -50,8 +50,4 @@ jobs:
git commit -m "Sync wiki from source repository" git commit -m "Sync wiki from source repository"
# Push changes # Push changes
git push https://${WIKI_ACTOR}:${WIKI_TOKEN}@github.com/${WIKI_REPO}.wiki.git git push https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.wiki.git
env:
WIKI_ACTOR: ${{ github.actor }}
WIKI_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WIKI_REPO: ${{ github.repository }}

View File

@@ -17,12 +17,12 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 uses: actions/checkout@v4
with: with:
clean: 'true' clean: 'true'
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 uses: shivammathur/setup-php@v2
with: with:
php-version: ${{ matrix.php-versions }} php-version: ${{ matrix.php-versions }}
extensions: mbstring, intl, zip extensions: mbstring, intl, zip
@@ -43,12 +43,12 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 uses: actions/checkout@v4
with: with:
clean: 'true' clean: 'true'
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2 uses: shivammathur/setup-php@v2
with: with:
php-version: '7.4' php-version: '7.4'
extensions: mbstring, intl, zip extensions: mbstring, intl, zip

View File

@@ -37,7 +37,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 uses: actions/setup-node@v4
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'npm' cache: 'npm'
@@ -73,7 +73,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: '20'
cache: 'npm' cache: 'npm'
@@ -124,7 +124,7 @@ jobs:
- name: Upload Cypress artifacts - name: Upload Cypress artifacts
if: always() if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 uses: actions/upload-artifact@v4
with: with:
name: cypress-results name: cypress-results
path: | path: |

3
.gitignore vendored
View File

@@ -63,6 +63,3 @@ temp/
coverage/ coverage/
.phpunit.result.cache .phpunit.result.cache
.agents/loop-state/ .agents/loop-state/
# Local tool configs
.superset/

View File

@@ -1,7 +1,4 @@
{ {
"MD004": {
"style": "asterisk"
},
"MD012": false, "MD012": false,
"MD022": false, "MD022": false,
"MD031": false, "MD031": false,

View File

@@ -6,11 +6,11 @@ Thank you for considering contributing to this project! This document provides g
By participating in this project, you agree to abide by our code of conduct: By participating in this project, you agree to abide by our code of conduct:
* Be respectful and inclusive - Be respectful and inclusive
* Be patient and welcoming - Be patient and welcoming
* Be considerate - Be considerate
* Be collaborative - Be collaborative
* Be open-minded - Be open-minded
## How to Contribute ## How to Contribute
@@ -26,12 +26,12 @@ If you find a bug, please report it by creating an issue on GitHub:
Please include: Please include:
* A clear, descriptive title - A clear, descriptive title
* Steps to reproduce the bug - Steps to reproduce the bug
* Expected behavior - Expected behavior
* Actual behavior - Actual behavior
* Screenshots (if applicable) - Screenshots (if applicable)
* Your environment (WordPress version, PHP version, browser, etc.) - Your environment (WordPress version, PHP version, browser, etc.)
### Suggesting Enhancements ### Suggesting Enhancements
@@ -45,10 +45,10 @@ If you have an idea for an enhancement:
Please include: Please include:
* A clear, descriptive title - A clear, descriptive title
* A detailed description of the enhancement - A detailed description of the enhancement
* Why this enhancement would be useful - Why this enhancement would be useful
* Any relevant examples or mockups - Any relevant examples or mockups
### Pull Requests ### Pull Requests

View File

@@ -24,10 +24,9 @@ The easiest way to test our plugin with WordPress Playground is to use the onlin
[playground-single]: https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/wpallstars/wp-plugin-starter-template-for-ai-coding/main/playground/blueprint.json&_t=2 [playground-single]: https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/wpallstars/wp-plugin-starter-template-for-ai-coding/main/playground/blueprint.json&_t=2
[playground-multisite]: https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/wpallstars/wp-plugin-starter-template-for-ai-coding/main/playground/multisite-blueprint.json&_t=2 [playground-multisite]: https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/wpallstars/wp-plugin-starter-template-for-ai-coding/main/playground/multisite-blueprint.json&_t=2
Both links automatically set up WordPress with WP_DEBUG enabled and the Plugin Toggle and These links automatically set up WordPress with multisite enabled and WP_DEBUG enabled.
Kadence Blocks plugins pre-installed and activated.
The multisite link additionally enables WordPress multisite and network-activates both plugins. Both the Plugin Toggle and Kadence Blocks plugins are pre-activated.
## WP-CLI Commands for WordPress Playground ## WP-CLI Commands for WordPress Playground
@@ -102,16 +101,14 @@ In a WordPress multisite environment, there are two ways to activate plugins:
1. **Network Activation**: Activates a plugin for all sites in the network 1. **Network Activation**: Activates a plugin for all sites in the network
* In the WordPress admin, go to Network Admin > Plugins * In the WordPress admin, go to Network Admin > Plugins
* Click "Network Activate" under the plugin * Click "Network Activate" under the plugin
* Or use WP-CLI: * Or use WP-CLI: `wp plugin install plugin-name --activate-network`
* To activate an already installed plugin: `wp plugin activate plugin-name --network`
* To install and activate in one step: `wp plugin install plugin-name --activate-network`
2. **Per-Site Activation**: Activates a plugin for a specific site 2. **Per-Site Activation**: Activates a plugin for a specific site
* In the WordPress admin, go to the specific site's admin area * In the WordPress admin, go to the specific site's admin area
* Go to Plugins and activate the plugin for that site only * Go to Plugins and activate the plugin for that site only
* Or use WP-CLI: `wp plugin activate plugin-name --url=site-url` * Or use WP-CLI: `wp plugin activate plugin-name --url=site-url`
Our multisite blueprint uses network activation for both the Plugin Toggle and Kadence Blocks plugins. Our multisite blueprint uses network activation for the Plugin Toggle plugin as an example.
## Running Tests with WordPress Playground ## Running Tests with WordPress Playground

View File

@@ -123,8 +123,8 @@
text-align: right; text-align: right;
} }
/* 782px is the WordPress mobile admin breakpoint. */ /* Responsive Styles */
@media screen and (max-width: 782px) { @media screen and (width <= 782px) {
.wpst-form-table th { .wpst-form-table th {
width: 100%; width: 100%;
display: block; display: block;

View File

@@ -78,7 +78,7 @@
data: { data: {
action: 'wpst_save_settings', action: 'wpst_save_settings',
nonce: wpstData.nonce, nonce: wpstData.nonce,
formData: formData, formData: formData
}, },
success: function (response) { success: function (response) {
if (response.success) { if (response.success) {
@@ -93,8 +93,8 @@
complete: function () { complete: function () {
// Re-enable submit button and remove loading state. // Re-enable submit button and remove loading state.
$submitButton.prop( 'disabled', false ).removeClass( 'loading' ); $submitButton.prop( 'disabled', false ).removeClass( 'loading' );
}, }
}, }
); );
}, },
@@ -105,16 +105,10 @@
* @param {string} message Notice message * @param {string} message Notice message
*/ */
showNotice: function (type, message) { showNotice: function (type, message) {
const allowedTypes = [ 'success', 'error', 'warning' ]; const $notice = $( '<div class="wpst-notice ' + type + '"><p>' + message + '</p></div>' );
const safeType = allowedTypes.includes( type ) ? type : 'error';
const $p = $( '<p>' );
const $notice = $( '<div>' ).addClass( 'wpst-notice ' + safeType ).append( $p );
// Set message as plain text to prevent XSS.
$p.text( message );
// Add notice to the page. // Add notice to the page.
$( '.wpst-notices' ).empty().append( $notice ); $( '.wpst-notices' ).html( $notice );
// Automatically remove notice after 5 seconds. // Automatically remove notice after 5 seconds.
setTimeout( setTimeout(
@@ -123,19 +117,19 @@
300, 300,
function () { function () {
$( this ).remove(); $( this ).remove();
}, }
); );
}, },
5000, 5000
); );
}, }
}; };
// Initialize when document is ready. // Initialize when document is ready.
$( document ).ready( $( document ).ready(
function () { function () {
WPSTAdmin.init(); WPSTAdmin.init();
}, }
); );
})( jQuery ); })( jQuery );

View File

@@ -48,7 +48,7 @@
if ($( e.target ).hasClass( 'wpst-modal' )) { if ($( e.target ).hasClass( 'wpst-modal' )) {
WPSTUpdateSourceSelector.closeModal(); WPSTUpdateSourceSelector.closeModal();
} }
}, }
); );
// Select source option. // Select source option.
@@ -116,7 +116,7 @@
data: { data: {
action: 'wpst_set_update_source', // AJAX action hook. action: 'wpst_set_update_source', // AJAX action hook.
nonce: wpstModalData.nonce, // Security nonce. nonce: wpstModalData.nonce, // Security nonce.
source: this.selectedSource, source: this.selectedSource
}, },
success: function (response) { success: function (response) {
if (response.success) { if (response.success) {
@@ -127,7 +127,7 @@
function () { function () {
WPSTUpdateSourceSelector.closeModal(); WPSTUpdateSourceSelector.closeModal();
}, },
1500, 1500
); );
} else { } else {
WPSTUpdateSourceSelector.showMessage( 'error', response.data.message ); WPSTUpdateSourceSelector.showMessage( 'error', response.data.message );
@@ -139,8 +139,8 @@
complete: function () { complete: function () {
// Reset button state. // Reset button state.
$saveButton.prop( 'disabled', false ).text( wpstModalData.i18n.confirm ); $saveButton.prop( 'disabled', false ).text( wpstModalData.i18n.confirm );
}, }
}, }
); );
}, },
@@ -153,30 +153,26 @@
showMessage: function (type, message) { showMessage: function (type, message) {
const $message = this.$modal.find( '.wpst-modal-message' ); const $message = this.$modal.find( '.wpst-modal-message' );
// Validate type against allow-list to prevent class injection vulnerabilities. // Set message content and type.
const allowedTypes = [ 'success', 'error' ]; $message.html( message ).removeClass( 'success error' ).addClass( type ).show();
const safeType = allowedTypes.includes( type ) ? type : 'error';
// Set message as plain text to prevent XSS, then apply validated type class.
$message.text( message ).removeClass( 'success error' ).addClass( safeType ).show();
// Hide message after a delay for success messages. // Hide message after a delay for success messages.
if (safeType === 'success') { if (type === 'success') {
setTimeout( setTimeout(
function () { function () {
$message.fadeOut( 300 ); $message.fadeOut( 300 );
}, },
3000, 3000
); );
} }
}, }
}; };
// Initialize when document is ready. // Initialize when document is ready.
$( document ).ready( $( document ).ready(
function () { function () {
WPSTUpdateSourceSelector.init(); WPSTUpdateSourceSelector.init();
}, }
); );
})( jQuery ); })( jQuery );

View File

@@ -12,22 +12,11 @@ if ( ! defined( 'ABSPATH' ) ) {
?> ?>
<!-- Update Source Modal --> <!-- Update Source Modal -->
<div <div id="wpst-update-source-modal" class="wpst-modal">
id="wpst-update-source-modal"
class="wpst-modal"
role="dialog"
aria-modal="true"
aria-labelledby="wpst-update-source-modal-title"
tabindex="-1"
>
<div class="wpst-modal-content"> <div class="wpst-modal-content">
<div class="wpst-modal-header"> <div class="wpst-modal-header">
<h2 id="wpst-update-source-modal-title" class="wpst-modal-title"><?php esc_html_e( 'Select Update Source', 'wp-plugin-starter-template' ); ?></h2> <h2 class="wpst-modal-title"><?php esc_html_e( 'Select Update Source', 'wp-plugin-starter-template' ); ?></h2>
<button <span class="wpst-modal-close">&times;</span>
type="button"
class="wpst-modal-close"
aria-label="<?php esc_attr_e( 'Close dialog', 'wp-plugin-starter-template' ); ?>"
>&times;</button>
</div> </div>
<div class="wpst-modal-body"> <div class="wpst-modal-body">

View File

@@ -17,19 +17,15 @@ WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib}
WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/} WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/}
download() { download() {
if command -v curl >/dev/null 2>&1; then if command -v curl > /dev/null; then
curl -fsSL "$1" -o "$2" curl -s "$1" > "$2";
elif command -v wget >/dev/null 2>&1; then elif command -v wget > /dev/null; then
wget -qO "$2" "$1" wget -nv -O "$2" "$1"
else
echo "Error: Neither curl nor wget is installed" >&2
exit 1
fi fi
} }
if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then
WP_BRANCH=${WP_VERSION%\-*} WP_BRANCH=${WP_VERSION%\-*}
WP_TESTS_TAG="branches/$WP_BRANCH"
elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then
WP_TESTS_TAG="branches/$WP_VERSION" WP_TESTS_TAG="branches/$WP_VERSION"
elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
@@ -44,31 +40,20 @@ elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
else else
# http serves a single offer, whereas https serves multiple. we only want one # http serves a single offer, whereas https serves multiple. we only want one
download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json
LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//' | head -1) grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json
LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//')
if [[ -z "$LATEST_VERSION" ]]; then if [[ -z "$LATEST_VERSION" ]]; then
echo "Latest WordPress version could not be found" echo "Latest WordPress version could not be found"
exit 1 exit 1
fi fi
WP_TESTS_TAG="tags/$LATEST_VERSION" WP_TESTS_TAG="tags/$LATEST_VERSION"
fi fi
# Derive a git ref from WP_TESTS_TAG by stripping the SVN-style prefix.
# WP_TESTS_TAG uses "tags/X.Y.Z", "branches/X.Y", or "trunk".
# git clone --branch requires the bare ref name ("X.Y.Z", "X.Y", or "trunk").
if [[ "$WP_TESTS_TAG" == tags/* ]]; then
GIT_REF="${WP_TESTS_TAG#tags/}"
elif [[ "$WP_TESTS_TAG" == branches/* ]]; then
GIT_REF="${WP_TESTS_TAG#branches/}"
else
GIT_REF="$WP_TESTS_TAG"
fi
set -ex set -ex
install_wp() { install_wp() {
if [ -d "$WP_CORE_DIR" ]; then if [ -d "$WP_CORE_DIR" ]; then
return return;
fi fi
mkdir -p "$WP_CORE_DIR" mkdir -p "$WP_CORE_DIR"
@@ -86,7 +71,6 @@ install_wp() {
if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
LATEST_VERSION=${WP_VERSION%??} LATEST_VERSION=${WP_VERSION%??}
else else
# shellcheck disable=SC2001
VERSION_ESCAPED=$(echo "$WP_VERSION" | sed 's/\./\\\\./g') VERSION_ESCAPED=$(echo "$WP_VERSION" | sed 's/\./\\\\./g')
LATEST_VERSION=$(grep -o '"version":"'"$VERSION_ESCAPED"'[^"]*' "$WP_CORE_DIR/wp-latest.json" | sed 's/"version":"//' | head -1) LATEST_VERSION=$(grep -o '"version":"'"$VERSION_ESCAPED"'[^"]*' "$WP_CORE_DIR/wp-latest.json" | sed 's/"version":"//' | head -1)
fi fi
@@ -117,22 +101,12 @@ install_test_suite() {
# set up testing suite if it doesn't yet exist # set up testing suite if it doesn't yet exist
if [ ! -d "$WP_TESTS_DIR" ]; then if [ ! -d "$WP_TESTS_DIR" ]; then
mkdir -p "$WP_TESTS_DIR" mkdir -p "$WP_TESTS_DIR"
rm -rf /tmp/wordpress-develop git clone --quiet --depth=1 https://github.com/WordPress/wordpress-develop.git /tmp/wordpress-develop
if ! git clone --quiet --depth=1 --branch "$GIT_REF" https://github.com/WordPress/wordpress-develop.git /tmp/wordpress-develop; then
echo "Error: Failed to clone wordpress-develop at branch/tag $GIT_REF" >&2
exit 1
fi
if [ -d /tmp/wordpress-develop/tests/phpunit/includes ]; then if [ -d /tmp/wordpress-develop/tests/phpunit/includes ]; then
if ! cp -r /tmp/wordpress-develop/tests/phpunit/includes "$WP_TESTS_DIR/"; then cp -r /tmp/wordpress-develop/tests/phpunit/includes "$WP_TESTS_DIR/"
echo "Error: Failed to copy phpunit includes to $WP_TESTS_DIR" >&2
exit 1
fi
fi fi
if [ -d /tmp/wordpress-develop/tests/phpunit/data ]; then if [ -d /tmp/wordpress-develop/tests/phpunit/data ]; then
if ! cp -r /tmp/wordpress-develop/tests/phpunit/data "$WP_TESTS_DIR/"; then cp -r /tmp/wordpress-develop/tests/phpunit/data "$WP_TESTS_DIR/"
echo "Error: Failed to copy phpunit data to $WP_TESTS_DIR" >&2
exit 1
fi
fi fi
fi fi
@@ -142,15 +116,15 @@ install_test_suite() {
else else
download https://raw.githubusercontent.com/WordPress/wordpress-develop/master/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php download https://raw.githubusercontent.com/WordPress/wordpress-develop/master/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
fi fi
WP_CORE_DIR="${WP_CORE_DIR%/}" WP_CORE_DIR=$(echo "$WP_CORE_DIR" | sed "s:/\+$::")
sed "$ioption" "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
sed "$ioption" "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
sed "$ioption" "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
sed "$ioption" "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
sed "$ioption" "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
if [ "$MULTISITE" = "true" ]; then if [ "$MULTISITE" = "true" ]; then
sed "$ioption" "s:// define( 'WP_TESTS_MULTISITE', true );:define( 'WP_TESTS_MULTISITE', true );:" "$WP_TESTS_DIR"/wp-tests-config.php sed $ioption "s:// define( 'WP_TESTS_MULTISITE', true );:define( 'WP_TESTS_MULTISITE', true );:" "$WP_TESTS_DIR"/wp-tests-config.php
fi fi
fi fi
@@ -158,14 +132,14 @@ install_test_suite() {
install_db() { install_db() {
if [ "${SKIP_DB_CREATE}" = "true" ]; then if [ ${SKIP_DB_CREATE} = "true" ]; then
return 0 return 0
fi fi
local PARTS local PARTS
IFS=':' read -ra PARTS <<< "$DB_HOST" IFS=':' read -ra PARTS <<< "$DB_HOST"
local DB_HOSTNAME=${PARTS[0]} local DB_HOSTNAME=${PARTS[0]};
local DB_SOCK_OR_PORT=${PARTS[1]} local DB_SOCK_OR_PORT=${PARTS[1]};
local EXTRA="" local EXTRA=""
if [ -n "$DB_HOSTNAME" ] ; then if [ -n "$DB_HOSTNAME" ] ; then
@@ -178,7 +152,7 @@ install_db() {
fi fi
fi fi
mysqladmin create "$DB_NAME" --user="$DB_USER" --password="$DB_PASS""$EXTRA" || true mysqladmin create "$DB_NAME" --user="$DB_USER" --password="$DB_PASS"$EXTRA || true
} }
install_wp install_wp

View File

@@ -28,6 +28,7 @@ set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")" PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
PLUGIN_SLUG="wp-plugin-starter-template" PLUGIN_SLUG="wp-plugin-starter-template"
PLUGIN_TEXT_DOMAIN="wp-plugin-starter-template"
# LocalWP paths (macOS) # LocalWP paths (macOS)
LOCAL_SITES_DIR="$HOME/Local Sites" LOCAL_SITES_DIR="$HOME/Local Sites"
@@ -82,8 +83,7 @@ check_localwp() {
exit 1 exit 1
fi fi
local version local version=$("$LOCAL_WP_CLI" --version 2>/dev/null || echo "unknown")
version=$("$LOCAL_WP_CLI" --version 2>/dev/null || echo "unknown")
log_info "LocalWP WP-CLI version: $version" log_info "LocalWP WP-CLI version: $version"
} }
@@ -96,8 +96,7 @@ get_site_path() {
# Get WordPress path within site # Get WordPress path within site
get_wp_path() { get_wp_path() {
local site_name="$1" local site_name="$1"
local site_path local site_path=$(get_site_path "$site_name")
site_path=$(get_site_path "$site_name")
# LocalWP uses app/public for WordPress files # LocalWP uses app/public for WordPress files
echo "$site_path/app/public" echo "$site_path/app/public"
@@ -106,24 +105,21 @@ get_wp_path() {
# Check if site exists # Check if site exists
site_exists() { site_exists() {
local site_name="$1" local site_name="$1"
local site_path local site_path=$(get_site_path "$site_name")
site_path=$(get_site_path "$site_name")
[ -d "$site_path" ] [ -d "$site_path" ]
} }
# Get plugin destination path # Get plugin destination path
get_plugin_path() { get_plugin_path() {
local site_name="$1" local site_name="$1"
local wp_path local wp_path=$(get_wp_path "$site_name")
wp_path=$(get_wp_path "$site_name")
echo "$wp_path/wp-content/plugins/$PLUGIN_SLUG" echo "$wp_path/wp-content/plugins/$PLUGIN_SLUG"
} }
# Sync plugin files to LocalWP site # Sync plugin files to LocalWP site
sync_plugin() { sync_plugin() {
local site_name="$1" local site_name="$1"
local plugin_dest local plugin_dest=$(get_plugin_path "$site_name")
plugin_dest=$(get_plugin_path "$site_name")
if ! site_exists "$site_name"; then if ! site_exists "$site_name"; then
log_error "Site '$site_name' does not exist" log_error "Site '$site_name' does not exist"
@@ -185,8 +181,7 @@ create_site() {
check_localwp check_localwp
local site_path local site_path=$(get_site_path "$site_name")
site_path=$(get_site_path "$site_name")
if site_exists "$site_name"; then if site_exists "$site_name"; then
log_warning "Site '$site_name' already exists at: $site_path" log_warning "Site '$site_name' already exists at: $site_path"
@@ -235,7 +230,7 @@ create_site() {
echo "" echo ""
# Wait for user to create site # Wait for user to create site
read -r -p "Press Enter after you've created the site in LocalWP..." read -p "Press Enter after you've created the site in LocalWP..."
if site_exists "$site_name"; then if site_exists "$site_name"; then
log_success "Site detected at: $site_path" log_success "Site detected at: $site_path"
@@ -255,8 +250,7 @@ create_site() {
# Install recommended plugins (matching Playground blueprint) # Install recommended plugins (matching Playground blueprint)
install_recommended_plugins() { install_recommended_plugins() {
local site_name="$1" local site_name="$1"
local wp_path local wp_path=$(get_wp_path "$site_name")
wp_path=$(get_wp_path "$site_name")
log_info "Note: Install these plugins to match Playground environment:" log_info "Note: Install these plugins to match Playground environment:"
echo " - Plugin Toggle (plugin-toggle)" echo " - Plugin Toggle (plugin-toggle)"
@@ -271,10 +265,8 @@ show_site_info() {
local domain="$2" local domain="$2"
local multisite="$3" local multisite="$3"
local site_path local site_path=$(get_site_path "$site_name")
site_path=$(get_site_path "$site_name") local plugin_path=$(get_plugin_path "$site_name")
local plugin_path
plugin_path=$(get_plugin_path "$site_name")
echo "" echo ""
echo "============================================" echo "============================================"
@@ -313,8 +305,7 @@ reset_site() {
echo echo
if [[ $REPLY =~ ^[Yy]$ ]]; then if [[ $REPLY =~ ^[Yy]$ ]]; then
local plugin_path local plugin_path=$(get_plugin_path "$site_name")
plugin_path=$(get_plugin_path "$site_name")
log_info "Removing plugin files..." log_info "Removing plugin files..."
rm -rf "$plugin_path" rm -rf "$plugin_path"
@@ -354,16 +345,14 @@ show_info() {
echo "===============================" echo "==============================="
for site_name in "$SINGLE_SITE_NAME" "$MULTISITE_NAME"; do for site_name in "$SINGLE_SITE_NAME" "$MULTISITE_NAME"; do
local site_path local site_path=$(get_site_path "$site_name")
site_path=$(get_site_path "$site_name")
if site_exists "$site_name"; then if site_exists "$site_name"; then
echo "" echo ""
echo " ${GREEN}${NC} $site_name" echo " ${GREEN}${NC} $site_name"
echo " Path: $site_path" echo " Path: $site_path"
local plugin_path local plugin_path=$(get_plugin_path "$site_name")
plugin_path=$(get_plugin_path "$site_name")
if [ -d "$plugin_path" ]; then if [ -d "$plugin_path" ]; then
echo " Plugin: ${GREEN}Installed${NC}" echo " Plugin: ${GREEN}Installed${NC}"
else else

View File

@@ -1,5 +1,8 @@
#!/bin/bash #!/bin/bash
# Make this script executable
chmod +x "$0"
# Check if environment type is provided # Check if environment type is provided
if [ -z "$1" ]; then if [ -z "$1" ]; then
echo "Usage: $0 [single|multisite|playground-single|playground-multisite]" echo "Usage: $0 [single|multisite|playground-single|playground-multisite]"
@@ -11,24 +14,8 @@ ENV_TYPE=$1
# Function to check if a command exists # Function to check if a command exists
command_exists() { command_exists() {
command -v "$1" &> /dev/null command -v "$1" &> /dev/null
return $?
} }
# PID of the background Python HTTP server (set when started).
PYTHON_PID=""
# Function to clean up resources on exit.
cleanup() {
if [ -n "$PYTHON_PID" ]; then
echo "Stopping Python HTTP server (PID: $PYTHON_PID)..."
kill "$PYTHON_PID" 2>/dev/null || true
fi
return 0
}
# Trap EXIT, INT, and TERM so the server is always stopped on script exit.
trap cleanup EXIT INT TERM
# Function to install wp-env if needed # Function to install wp-env if needed
install_wp_env() { install_wp_env() {
if ! command_exists wp-env; then if ! command_exists wp-env; then
@@ -46,7 +33,7 @@ install_wp_playground() {
fi fi
} }
if [ "$ENV_TYPE" = "single" ]; then if [ "$ENV_TYPE" == "single" ]; then
echo "Setting up single site environment..." echo "Setting up single site environment..."
# Install wp-env if needed # Install wp-env if needed
@@ -80,7 +67,7 @@ if [ "$ENV_TYPE" = "single" ]; then
echo "Site: http://localhost:8888" echo "Site: http://localhost:8888"
echo "Admin login: admin / password" echo "Admin login: admin / password"
elif [ "$ENV_TYPE" = "multisite" ]; then elif [ "$ENV_TYPE" == "multisite" ]; then
echo "Setting up multisite environment..." echo "Setting up multisite environment..."
# Install wp-env if needed # Install wp-env if needed
@@ -121,7 +108,7 @@ elif [ "$ENV_TYPE" = "multisite" ]; then
echo "Test site: http://localhost:8888/testsite" echo "Test site: http://localhost:8888/testsite"
echo "Admin login: admin / password" echo "Admin login: admin / password"
elif [ "$ENV_TYPE" = "playground-single" ]; then elif [ "$ENV_TYPE" == "playground-single" ]; then
echo "Setting up WordPress Playground single site environment..." echo "Setting up WordPress Playground single site environment..."
# Install wp-playground if needed # Install wp-playground if needed
@@ -165,8 +152,6 @@ EOF
echo "Starting WordPress Playground..." echo "Starting WordPress Playground..."
if command_exists python3; then if command_exists python3; then
python3 -m http.server 8888 --directory playground & python3 -m http.server 8888 --directory playground &
PYTHON_PID=$!
echo "Started Python HTTP server with PID: $PYTHON_PID"
echo "Opening WordPress Playground in your browser..." echo "Opening WordPress Playground in your browser..."
if command_exists open; then if command_exists open; then
open http://localhost:8888/index.html open http://localhost:8888/index.html
@@ -190,7 +175,7 @@ EOF
echo "Admin login: admin / password" echo "Admin login: admin / password"
echo "Press Ctrl+C to stop the server when done." echo "Press Ctrl+C to stop the server when done."
elif [ "$ENV_TYPE" = "playground-multisite" ]; then elif [ "$ENV_TYPE" == "playground-multisite" ]; then
echo "Setting up WordPress Playground multisite environment..." echo "Setting up WordPress Playground multisite environment..."
# Install wp-playground if needed # Install wp-playground if needed
@@ -202,7 +187,6 @@ elif [ "$ENV_TYPE" = "playground-multisite" ]; then
zip -r dist/plugin.zip . -x "node_modules/*" "dist/*" ".git/*" zip -r dist/plugin.zip . -x "node_modules/*" "dist/*" ".git/*"
# Update blueprint to use local plugin # Update blueprint to use local plugin
# shellcheck disable=SC2154
cat > playground/multisite-blueprint.json << EOF cat > playground/multisite-blueprint.json << EOF
{ {
"landingPage": "/wp-admin/network/", "landingPage": "/wp-admin/network/",
@@ -275,8 +259,6 @@ EOF
echo "Starting WordPress Playground..." echo "Starting WordPress Playground..."
if command_exists python3; then if command_exists python3; then
python3 -m http.server 8888 --directory playground & python3 -m http.server 8888 --directory playground &
PYTHON_PID=$!
echo "Started Python HTTP server with PID: $PYTHON_PID"
echo "Opening WordPress Playground in your browser..." echo "Opening WordPress Playground in your browser..."
if command_exists open; then if command_exists open; then
open http://localhost:8888/multisite.html open http://localhost:8888/multisite.html

View File

@@ -1,5 +1,4 @@
#!/bin/bash #!/bin/bash
set -euo pipefail
# WordPress Plugin Build Script # WordPress Plugin Build Script
# This script creates a clean build of the plugin for distribution # This script creates a clean build of the plugin for distribution
@@ -40,15 +39,15 @@ composer install --no-dev --optimize-autoloader
# Copy plugin files to build directory # Copy plugin files to build directory
echo "Copying plugin files..." echo "Copying plugin files..."
cp -R ./*.php "$BUILD_DIR/" cp -R *.php "$BUILD_DIR/"
cp -R README.md LICENSE CHANGELOG.md readme.txt composer.json "$BUILD_DIR/" cp -R README.md LICENSE CHANGELOG.md readme.txt composer.json "$BUILD_DIR/"
# Copy directories # Copy directories
echo "Copying directories..." echo "Copying directories..."
mkdir -p "$BUILD_DIR/admin" "$BUILD_DIR/includes" "$BUILD_DIR/languages" "$BUILD_DIR/assets" mkdir -p "$BUILD_DIR/admin" "$BUILD_DIR/includes" "$BUILD_DIR/languages" "$BUILD_DIR/assets"
cp -R ./admin/* "$BUILD_DIR/admin/" cp -R admin/* "$BUILD_DIR/admin/"
cp -R ./includes/* "$BUILD_DIR/includes/" cp -R includes/* "$BUILD_DIR/includes/"
cp -R ./languages/* "$BUILD_DIR/languages/" cp -R languages/* "$BUILD_DIR/languages/"
# Create assets directory structure # Create assets directory structure
mkdir -p "$BUILD_DIR/assets/banner" "$BUILD_DIR/assets/icon" "$BUILD_DIR/assets/screenshots" mkdir -p "$BUILD_DIR/assets/banner" "$BUILD_DIR/assets/icon" "$BUILD_DIR/assets/screenshots"
@@ -73,10 +72,9 @@ fi
# Create ZIP file. # Create ZIP file.
echo "Creating ZIP file..." echo "Creating ZIP file..."
(
cd build || exit 1 cd build || exit 1
zip -r "../$ZIP_FILE" "$PLUGIN_SLUG" -x "*.DS_Store" -x "*.git*" -x "*.github*" zip -r "../$ZIP_FILE" "$PLUGIN_SLUG" -x "*.DS_Store" -x "*.git*" -x "*.github*"
) cd ..
# Check if ZIP file was created successfully # Check if ZIP file was created successfully
if [ -f "$ZIP_FILE" ]; then if [ -f "$ZIP_FILE" ]; then
@@ -84,8 +82,9 @@ if [ -f "$ZIP_FILE" ]; then
echo "File path: $(pwd)/$ZIP_FILE" echo "File path: $(pwd)/$ZIP_FILE"
# Deploy to local WordPress installation if environment variable is set # Deploy to local WordPress installation if environment variable is set
if [ -n "${WP_LOCAL_PLUGIN_DIR:-}" ]; then if [ -n "$WP_LOCAL_PLUGIN_DIR" ]; then
printf '\nDeploying to local WordPress installation...\n' echo "\nDeploying to local WordPress installation..."
echo "Deploying to local WordPress installation..."
# Remove existing plugin directory. # Remove existing plugin directory.
rm -rf "${WP_LOCAL_PLUGIN_DIR:?}/$PLUGIN_SLUG" rm -rf "${WP_LOCAL_PLUGIN_DIR:?}/$PLUGIN_SLUG"

View File

@@ -12,6 +12,6 @@ module.exports = defineConfig({
}, },
// Add configuration for WordPress Playground // Add configuration for WordPress Playground
experimentalWebKitSupport: true, experimentalWebKitSupport: true,
chromeWebSecurity: false, chromeWebSecurity: false
}, }
}); });

View File

@@ -1,10 +1,5 @@
/* eslint-env mocha, jquery, cypress */ /* eslint-env mocha, jquery, cypress */
describe('WordPress Playground Single Site Tests', { describe('WordPress Playground Single Site Tests', () => {
retries: {
runMode: 2,
openMode: 0,
},
}, () => {
beforeEach(() => { beforeEach(() => {
cy.visit('/', { timeout: 30000 }); cy.visit('/', { timeout: 30000 });
}); });
@@ -23,22 +18,14 @@ describe('WordPress Playground Single Site Tests', {
cy.visit('/wp-admin/plugins.php', { timeout: 30000 }); cy.visit('/wp-admin/plugins.php', { timeout: 30000 });
cy.get('body', { timeout: 15000 }).then(($body) => { cy.get('body', { timeout: 15000 }).then(($body) => {
const hasPluginToggle = $body.text().includes('Plugin Toggle'); if ($body.text().includes('Plugin Toggle')) {
const hasKadenceBlocks = $body.text().includes('Kadence Blocks');
expect(
hasPluginToggle || hasKadenceBlocks,
'At least one blueprint plugin should be present in the plugins table',
).to.be.true;
if (hasPluginToggle) {
cy.contains('tr', 'Plugin Toggle').should('exist'); cy.contains('tr', 'Plugin Toggle').should('exist');
cy.contains('tr', 'Plugin Toggle').find('.deactivate').should('exist'); cy.contains('tr', 'Plugin Toggle').find('.deactivate').should('exist');
} else { } else {
cy.log('Plugin Toggle not found, skipping check'); cy.log('Plugin Toggle not found, skipping check');
} }
if (hasKadenceBlocks) { if ($body.text().includes('Kadence Blocks')) {
cy.contains('tr', 'Kadence Blocks').find('.deactivate').should('exist'); cy.contains('tr', 'Kadence Blocks').find('.deactivate').should('exist');
} else { } else {
cy.log('Kadence Blocks plugin not found, skipping check'); cy.log('Kadence Blocks plugin not found, skipping check');

View File

@@ -1,4 +1,3 @@
/* eslint-env mocha, jquery, cypress */
describe('WordPress Single Site Tests', () => { describe('WordPress Single Site Tests', () => {
it('Can access the site', () => { it('Can access the site', () => {
cy.visit('/'); cy.visit('/');
@@ -12,64 +11,20 @@ describe( 'WordPress Single Site Tests', () => {
}); });
it('Plugin is activated', () => { it('Plugin is activated', () => {
// Use our custom command to check and activate the plugin if needed. // Use our custom command to check and activate the plugin if needed
cy.activatePlugin('wp-plugin-starter-template-for-ai-coding'); cy.activatePlugin('wp-plugin-starter-template-for-ai-coding');
// Verify it's active. // Verify it's active
cy.get('tr[data-slug="wp-plugin-starter-template-for-ai-coding"] .deactivate').should('exist'); cy.get('tr[data-slug="wp-plugin-starter-template-for-ai-coding"] .deactivate').should('exist');
}); });
it( 'Plugin row is visible on the plugins page', () => { it('Plugin settings page loads correctly', () => {
cy.loginAsAdmin(); cy.loginAsAdmin();
cy.visit( '/wp-admin/plugins.php' );
// Verify the plugin row exists with the correct slug. // Navigate to the plugin settings page (if it exists)
cy.get( 'tr[data-slug="wp-plugin-starter-template-for-ai-coding"]' ).should( 'exist' ); cy.visit('/wp-admin/options-general.php?page=wp-plugin-starter-template');
// Verify the plugin name is displayed. // This is a basic check - adjust based on your actual plugin's settings page
cy.get( 'tr[data-slug="wp-plugin-starter-template-for-ai-coding"] .plugin-title strong' ) cy.get('h1').should('contain', 'WP Plugin Starter Template');
.should( 'contain', 'WordPress Plugin Starter Template' );
} );
it( 'Update source selector link is present in the plugin row', () => {
cy.loginAsAdmin();
cy.visit( '/wp-admin/plugins.php' );
// The update source selector link should be rendered in the plugin row.
cy.get( 'tr[data-slug="wp-plugin-starter-template-for-ai-coding"]' )
.find( '.wpst-update-source-selector' )
.should( 'exist' );
} );
it( 'Update source modal opens and displays source options', () => {
cy.loginAsAdmin();
cy.visit( '/wp-admin/plugins.php' );
// Click the update source selector link to open the modal.
cy.get( '.wpst-update-source-selector' ).first().click();
// Modal should be visible.
cy.get( '#wpst-update-source-modal' ).should( 'be.visible' );
// Modal should contain the three update source options.
cy.get( '#wpst-update-source-modal input[name="update_source"][value="wordpress.org"]' ).should( 'exist' );
cy.get( '#wpst-update-source-modal input[name="update_source"][value="github"]' ).should( 'exist' );
cy.get( '#wpst-update-source-modal input[name="update_source"][value="gitea"]' ).should( 'exist' );
// Save button should be present.
cy.get( '#wpst-save-source' ).should( 'exist' );
} );
it( 'Update source modal can be closed', () => {
cy.loginAsAdmin();
cy.visit( '/wp-admin/plugins.php' );
// Open the modal.
cy.get( '.wpst-update-source-selector' ).first().click();
cy.get( '#wpst-update-source-modal' ).should( 'be.visible' );
// Close the modal via the close button.
cy.get( '#wpst-update-source-modal .wpst-modal-close' ).click();
cy.get( '#wpst-update-source-modal' ).should( 'not.be.visible' );
}); });
}); });

View File

@@ -69,12 +69,10 @@ class Admin {
// Get the plugin version. // Get the plugin version.
$plugin_version = $this->core->get_plugin_version(); $plugin_version = $this->core->get_plugin_version();
$plugin_url = $this->get_plugin_base_url();
// Enqueue styles. // Enqueue styles.
\wp_enqueue_style( \wp_enqueue_style(
'wpst-admin-styles', 'wpst-admin-styles',
$plugin_url . 'admin/css/admin-styles.css', plugin_dir_url( dirname( __DIR__ ) ) . 'admin/css/admin-styles.css',
array(), // Dependencies. array(), // Dependencies.
$plugin_version // Version. $plugin_version // Version.
); );
@@ -82,7 +80,7 @@ class Admin {
// Enqueue admin scripts. // Enqueue admin scripts.
\wp_enqueue_script( \wp_enqueue_script(
'wpst-admin-script', 'wpst-admin-script',
$plugin_url . 'admin/js/admin-scripts.js', plugin_dir_url( dirname( __DIR__ ) ) . 'admin/js/admin-scripts.js',
array( 'jquery' ), array( 'jquery' ),
$plugin_version, // Version. $plugin_version, // Version.
true true
@@ -101,21 +99,4 @@ class Admin {
$data $data
); );
} }
/**
* Get plugin base URL.
*
* @return string Plugin base URL with trailing slash.
*/
private function get_plugin_base_url(): string {
if ( defined( 'WP_PLUGIN_STARTER_TEMPLATE_URL' ) ) {
return WP_PLUGIN_STARTER_TEMPLATE_URL;
}
if ( defined( 'WPST_PLUGIN_URL' ) ) {
return WPST_PLUGIN_URL;
}
return \plugin_dir_url( dirname( __DIR__, 2 ) );
}
} }

View File

@@ -6,10 +6,10 @@
* Extend this file or create additional classes in this directory * Extend this file or create additional classes in this directory
* to implement multisite features for your plugin. * to implement multisite features for your plugin.
* *
* @package WPALLSTARS\PluginStarterTemplate * @package WP_Plugin_Starter_Template_For_AI_Coding
*/ */
namespace WPALLSTARS\PluginStarterTemplate\Multisite; namespace WP_Plugin_Starter_Template_For_AI_Coding\Multisite;
// Exit if accessed directly. // Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {

1180
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -28,8 +28,7 @@
"test:phpunit": "composer test", "test:phpunit": "composer test",
"test:phpunit:multisite": "WP_MULTISITE=1 composer test", "test:phpunit:multisite": "WP_MULTISITE=1 composer test",
"build": "./build.sh", "build": "./build.sh",
"lint:js": "eslint cypress/ cypress.config.js", "lint:js": "eslint cypress/",
"lint:css": "stylelint \"**/*.css\" --allow-empty-input",
"lint:php": "composer run-script phpcs", "lint:php": "composer run-script phpcs",
"lint:php:simple": "composer run-script phpcs:simple", "lint:php:simple": "composer run-script phpcs:simple",
"lint:phpstan": "composer run-script phpstan", "lint:phpstan": "composer run-script phpstan",
@@ -37,8 +36,7 @@
"fix:php": "composer run-script phpcbf", "fix:php": "composer run-script phpcbf",
"fix:php:simple": "composer run-script phpcbf:simple", "fix:php:simple": "composer run-script phpcbf:simple",
"test:php": "composer run-script test", "test:php": "composer run-script test",
"lint:php-all": "composer run-script lint", "lint": "composer run-script lint",
"lint": "npm run lint:php-all && npm run lint:js && npm run lint:css",
"fix": "composer run-script fix", "fix": "composer run-script fix",
"quality": "npm run lint && npm run test:php" "quality": "npm run lint && npm run test:php"
}, },
@@ -62,12 +60,10 @@
"devDependencies": { "devDependencies": {
"@wordpress/env": "^8.12.0", "@wordpress/env": "^8.12.0",
"@wp-playground/blueprints": "^3.0.22", "@wp-playground/blueprints": "^3.0.22",
"@wp-playground/cli": "^3.0.22",
"@wp-playground/client": "^3.0.22", "@wp-playground/client": "^3.0.22",
"@wp-playground/cli": "^3.0.22",
"cypress": "^13.17.0", "cypress": "^13.17.0",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-cypress": "^6.2.0", "eslint-plugin-cypress": "^2.15.1"
"stylelint": "^16.0.0",
"stylelint-config-standard": "^36.0.0"
} }
} }

View File

@@ -15,8 +15,6 @@
<exclude name="CamelCaseMethodName" /> <exclude name="CamelCaseMethodName" />
<exclude name="CamelCaseParameterName" /> <exclude name="CamelCaseParameterName" />
<exclude name="CamelCaseVariableName" /> <exclude name="CamelCaseVariableName" />
<!-- WordPress plugins use filter_input() for production and $_GET for testing; Superglobals rule is not applicable. -->
<exclude name="Superglobals" />
</rule> </rule>
<rule ref="rulesets/design.xml" /> <rule ref="rulesets/design.xml" />
<rule ref="rulesets/naming.xml"> <rule ref="rulesets/naming.xml">

View File

@@ -5,6 +5,13 @@ parameters:
- admin - admin
- wp-plugin-starter-template.php - wp-plugin-starter-template.php
excludePaths: excludePaths:
analyse:
- vendor
- node_modules
- tests
- bin
- build
- dist
analyseAndScan: analyseAndScan:
- vendor - vendor
- node_modules - node_modules

View File

@@ -21,6 +21,6 @@
</style> </style>
</head> </head>
<body> <body>
<iframe src="https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/wpallstars/wp-plugin-starter-template-for-ai-coding/main/playground/blueprint.json" title="WordPress Playground Single Site Environment"></iframe> <iframe src="https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/wpallstars/wp-plugin-starter-template-for-ai-coding/main/playground/blueprint.json&_t=2" title="WordPress Playground Single Site Environment"></iframe>
</body> </body>
</html> </html>

View File

@@ -68,7 +68,7 @@
"landingPage": "/wp-admin/", "landingPage": "/wp-admin/",
"login": true, "login": true,
"features": { "features": {
"networking": false "networking": true
}, },
"steps": [ "steps": [
{ {

View File

@@ -46,7 +46,7 @@ if ( getenv( 'WP_PHPUNIT__DIR' ) ) {
// Include plugin files needed for tests. // Include plugin files needed for tests.
require_once WPST_PLUGIN_DIR . 'includes/class-core.php'; require_once WPST_PLUGIN_DIR . 'includes/class-core.php';
require_once WPST_PLUGIN_DIR . 'includes/class-plugin.php'; require_once WPST_PLUGIN_DIR . 'includes/class-plugin.php';
if ( file_exists( WPST_PLUGIN_DIR . 'includes/Admin/class-admin.php' ) ) { if ( file_exists( WPST_PLUGIN_DIR . 'admin/lib/admin.php' ) ) {
require_once WPST_PLUGIN_DIR . 'includes/Admin/class-admin.php'; require_once WPST_PLUGIN_DIR . 'admin/lib/admin.php';
} }
} }

View File

@@ -101,12 +101,17 @@ class AdminTest extends \WP_Mock\Tools\TestCase {
'return' => 'wp_plugin_starter_template_settings', 'return' => 'wp_plugin_starter_template_settings',
]); ]);
// Mock WordPress functions used in the method
WP_Mock::userFunction('plugin_dir_url', [
'return' => 'http://example.com/wp-content/plugins/wp-plugin-starter-template/includes/Admin/',
]);
// Mock wp_enqueue_style // Mock wp_enqueue_style
WP_Mock::userFunction('wp_enqueue_style', [ WP_Mock::userFunction('wp_enqueue_style', [
'times' => 1, 'times' => 1,
'args' => [ 'args' => [
'wpst-admin-styles', 'wpst-admin-styles',
'http://example.org/wp-content/plugins/wp-plugin-starter-template/admin/css/admin-styles.css', 'http://example.com/wp-content/plugins/wp-plugin-starter-template/includes/Admin/../../admin/css/admin-styles.css',
[], [],
'1.0.0', '1.0.0',
], ],
@@ -117,7 +122,7 @@ class AdminTest extends \WP_Mock\Tools\TestCase {
'times' => 1, 'times' => 1,
'args' => [ 'args' => [
'wpst-admin-script', 'wpst-admin-script',
'http://example.org/wp-content/plugins/wp-plugin-starter-template/admin/js/admin-scripts.js', 'http://example.com/wp-content/plugins/wp-plugin-starter-template/includes/Admin/../../admin/js/admin-scripts.js',
['jquery'], ['jquery'],
'1.0.0', '1.0.0',
true, true,

View File

@@ -55,19 +55,13 @@ spl_autoload_register(
// Get the relative class name. // Get the relative class name.
$relative_class = substr( $className, $len ); $relative_class = substr( $className, $len );
// Build class file path using WordPress-style class file names. // Convert namespace to path.
$relative_path = str_replace( '\\', '/', $relative_class ); $file = WP_PLUGIN_STARTER_TEMPLATE_PATH . 'includes/' . str_replace( '\\', '/', $relative_class ) . '.php';
$path_parts = explode( '/', $relative_path );
$class_name = array_pop( $path_parts );
$directory = '';
if ( ! empty( $path_parts ) ) { // Convert class name format to file name format.
$directory = implode( '/', $path_parts ) . '/'; $file = str_replace( 'class-', '', $file );
} $file = preg_replace( '/([a-z])([A-Z])/', '$1-$2', $file );
$file = strtolower( $file );
$class_file = preg_replace( '/([a-z])([A-Z])/', '$1-$2', $class_name );
$class_file = 'class-' . strtolower( $class_file ) . '.php';
$file = WP_PLUGIN_STARTER_TEMPLATE_PATH . 'includes/' . $directory . $class_file;
// If the file exists, require it. // If the file exists, require it.
if ( file_exists( $file ) ) { if ( file_exists( $file ) ) {