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
26 changed files with 262 additions and 390 deletions

View File

@@ -29,11 +29,6 @@ jobs:
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Install WordPress Coding Standards
run: |
composer require --dev wp-coding-standards/wpcs dealerdirect/phpcodesniffer-composer-installer
vendor/bin/phpcs --config-set installed_paths vendor/wp-coding-standards/wpcs
- name: Run PHPCS
run: composer phpcs
continue-on-error: true
@@ -61,9 +56,6 @@ jobs:
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Install PHPStan WordPress stubs
run: composer require --dev szepeviktor/phpstan-wordpress
- name: Run PHPStan
run: composer phpstan
continue-on-error: true

View File

@@ -34,11 +34,8 @@ jobs:
- name: Install dependencies
run: composer install --prefer-dist --no-progress
# - name: Debug test file content
# run: echo "--- Debugging tests/test-admin.php lines 75-95 ---" && sed -n '75,95p' tests/test-admin.php && echo "--- End Debugging ---"
# - name: Run tests
# run: ./vendor/bin/phpunit
- name: Run tests
run: ./vendor/bin/phpunit
code-style:
name: Code Style

View File

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

View File

@@ -67,7 +67,7 @@ if ($condition) {
### Documentation
* All classes, methods, and functions should be documented using PHPDoc
* Include a description of the parameters, return values, and possible exceptions
* Include descriptions of parameters, return values, and any exceptions thrown
```php
/**
@@ -271,7 +271,7 @@ To ensure your code passes the quality checks from these tools, follow these gui
3. **Using AI Assistants with Code Quality Tools**
* When you receive feedback from code quality tools, you can use AI assistants to help address the issues
* Copy the output from the code quality tool and paste it into your AI assistant chat
* Ask the AI to help you understand and resolve the issues
* Request the AI's assistance to interpret and resolve the reported issues
* Example prompt:
```text

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:
* Be respectful and inclusive
* Be patient and welcoming
* Be considerate
* Be collaborative
* Be open-minded
- Be respectful and inclusive
- Be patient and welcoming
- Be considerate
- Be collaborative
- Be open-minded
## How to Contribute
@@ -26,12 +26,12 @@ If you find a bug, please report it by creating an issue on GitHub:
Please include:
* A clear, descriptive title
* Steps to reproduce the bug
* Expected behavior
* Actual behavior
* Screenshots (if applicable)
* Your environment (WordPress version, PHP version, browser, etc.)
- A clear, descriptive title
- Steps to reproduce the bug
- Expected behavior
- Actual behavior
- Screenshots (if applicable)
- Your environment (WordPress version, PHP version, browser, etc.)
### Suggesting Enhancements
@@ -45,10 +45,10 @@ If you have an idea for an enhancement:
Please include:
* A clear, descriptive title
* A detailed description of the enhancement
* Why this enhancement would be useful
* Any relevant examples or mockups
- A clear, descriptive title
- A detailed description of the enhancement
- Why this enhancement would be useful
- Any relevant examples or mockups
### Pull Requests
@@ -106,7 +106,7 @@ When you receive feedback from these code quality tools, you can use AI assistan
1. Copy the output from the code quality tool
2. Paste it into your AI assistant chat
3. Ask the AI to help you understand and resolve the issues
3. Request the AI's assistance to interpret and resolve the reported issues
4. Apply the suggested fixes
5. Commit the changes and verify that the issues are resolved
@@ -143,7 +143,7 @@ To ensure your code meets the quality standards, run these commands before submi
* Check JavaScript coding standards: `npm run lint:js`
* Check CSS coding standards: `npm run lint:css`
These checks will help identify and fix issues before they are caught by the automated code quality tools in the pull request process.
These checks will assist in identifying and resolving issues before they are caught by the automated code quality tools in the pull request process.
## Documentation

View File

@@ -376,7 +376,7 @@ When you receive feedback from these code quality tools, you can use AI assistan
1. Copy the output from the code quality tool
2. Paste it into your AI assistant chat
3. Ask the AI to help you understand and resolve the issues
3. Request the AI's assistance to interpret and resolve the reported issues
4. Apply the suggested fixes
5. Commit the changes and verify that the issues are resolved

View File

@@ -6,140 +6,140 @@
/* General Admin Styles */
.wpst-admin-page {
max-width: 1200px;
margin: 20px auto;
padding: 20px;
background: #fff;
border-radius: 5px;
box-shadow: 0 1px 3px rgb(0 0 0 / 10%);
max-width: 1200px;
margin: 20px auto;
padding: 20px;
background: #fff;
border-radius: 5px;
box-shadow: 0 1px 3px rgb(0 0 0 / 10%);
}
.wpst-admin-header {
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
}
.wpst-admin-header h1 {
margin-top: 0;
color: #23282d;
margin-top: 0;
color: #23282d;
}
/* Admin Form Styles */
.wpst-form-table {
width: 100%;
border-collapse: collapse;
width: 100%;
border-collapse: collapse;
}
.wpst-form-table th {
text-align: left;
padding: 15px 10px 15px 0;
width: 200px;
vertical-align: top;
text-align: left;
padding: 15px 10px 15px 0;
width: 200px;
vertical-align: top;
}
.wpst-form-table td {
padding: 15px 0;
vertical-align: middle;
padding: 15px 0;
vertical-align: middle;
}
.wpst-form-table input[type="text"],
.wpst-form-table input[type="number"],
.wpst-form-table select,
.wpst-form-table textarea {
width: 400px;
max-width: 100%;
width: 400px;
max-width: 100%;
}
.wpst-form-table textarea {
min-height: 100px;
min-height: 100px;
}
.wpst-form-description {
color: #666;
font-style: italic;
margin-top: 5px;
color: #666;
font-style: italic;
margin-top: 5px;
}
/* Admin Notices */
.wpst-notice {
padding: 10px 15px;
margin: 15px 0;
border-radius: 3px;
border-left: 4px solid #00a0d2;
background: #f7fcff;
padding: 10px 15px;
margin: 15px 0;
border-radius: 3px;
border-left: 4px solid #00a0d2;
background: #f7fcff;
}
.wpst-notice.success {
border-left-color: #46b450;
background: #ecf7ed;
border-left-color: #46b450;
background: #ecf7ed;
}
.wpst-notice.error {
border-left-color: #dc3232;
background: #fbeaea;
border-left-color: #dc3232;
background: #fbeaea;
}
.wpst-notice.warning {
border-left-color: #ffb900;
background: #fff8e5;
border-left-color: #ffb900;
background: #fff8e5;
}
/* Admin Cards */
.wpst-card-container {
display: flex;
flex-wrap: wrap;
margin: 0 -10px;
display: flex;
flex-wrap: wrap;
margin: 0 -10px;
}
.wpst-card {
flex: 1 0 300px;
margin: 10px;
padding: 20px;
background: #fff;
border-radius: 3px;
box-shadow: 0 1px 3px rgb(0 0 0 / 10%);
flex: 1 0 300px;
margin: 10px;
padding: 20px;
background: #fff;
border-radius: 3px;
box-shadow: 0 1px 3px rgb(0 0 0 / 10%);
}
.wpst-card-header {
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.wpst-card-title {
margin: 0;
font-size: 16px;
font-weight: 600;
margin: 0;
font-size: 16px;
font-weight: 600;
}
.wpst-card-content {
margin-bottom: 15px;
margin-bottom: 15px;
}
.wpst-card-footer {
padding-top: 15px;
border-top: 1px solid #eee;
text-align: right;
padding-top: 15px;
border-top: 1px solid #eee;
text-align: right;
}
/* Responsive Styles */
@media screen and (width <= 782px) {
.wpst-form-table th {
width: 100%;
display: block;
padding-bottom: 0;
}
.wpst-form-table th {
width: 100%;
display: block;
padding-bottom: 0;
}
.wpst-form-table td {
width: 100%;
display: block;
}
.wpst-form-table td {
width: 100%;
display: block;
}
.wpst-form-table input[type="text"],
.wpst-form-table input[type="number"],
.wpst-form-table select,
.wpst-form-table textarea {
width: 100%;
}
.wpst-form-table input[type="text"],
.wpst-form-table input[type="number"],
.wpst-form-table select,
.wpst-form-table textarea {
width: 100%;
}
}

View File

@@ -6,138 +6,138 @@
/* Modal Styles */
.wpst-modal {
display: none;
position: fixed;
z-index: 100000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0 0 0 / 40%);
display: none;
position: fixed;
z-index: 100000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0 0 0 / 40%);
}
.wpst-modal-content {
position: relative;
background-color: #fefefe;
margin: 10% auto;
padding: 20px;
border-radius: 5px;
box-shadow: 0 4px 8px rgb(0 0 0 / 10%);
width: 500px;
max-width: 90%;
position: relative;
background-color: #fefefe;
margin: 10% auto;
padding: 20px;
border-radius: 5px;
box-shadow: 0 4px 8px rgb(0 0 0 / 10%);
width: 500px;
max-width: 90%;
}
.wpst-modal-header {
padding-bottom: 15px;
border-bottom: 1px solid #eee;
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
margin-bottom: 15px;
}
.wpst-modal-title {
margin: 0;
font-size: 18px;
font-weight: 600;
margin: 0;
font-size: 18px;
font-weight: 600;
}
.wpst-modal-close {
position: absolute;
top: 10px;
right: 15px;
font-size: 20px;
font-weight: 700;
color: #666;
cursor: pointer;
position: absolute;
top: 10px;
right: 15px;
font-size: 20px;
font-weight: 700;
color: #666;
cursor: pointer;
}
.wpst-modal-close:hover,
.wpst-modal-close:focus {
color: #000;
text-decoration: none;
color: #000;
text-decoration: none;
}
.wpst-modal-body {
margin-bottom: 20px;
margin-bottom: 20px;
}
.wpst-modal-footer {
padding-top: 15px;
border-top: 1px solid #eee;
text-align: right;
padding-top: 15px;
border-top: 1px solid #eee;
text-align: right;
}
/* Source Selection Styles */
.wpst-source-options {
margin: 15px 0;
margin: 15px 0;
}
.wpst-source-option {
display: block;
margin-bottom: 10px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 3px;
cursor: pointer;
display: block;
margin-bottom: 10px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 3px;
cursor: pointer;
}
.wpst-source-option:hover {
background-color: #f9f9f9;
background-color: #f9f9f9;
}
.wpst-source-option.selected {
border-color: #0073aa;
background-color: #f0f6fc;
border-color: #0073aa;
background-color: #f0f6fc;
}
.wpst-source-option input[type="radio"] {
margin-right: 10px;
margin-right: 10px;
}
.wpst-source-option-label {
font-weight: 600;
font-weight: 600;
}
.wpst-source-option-description {
margin-top: 5px;
color: #666;
font-size: 13px;
margin-top: 5px;
color: #666;
font-size: 13px;
}
/* Loading Indicator */
.wpst-loading {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid rgb(0 0 0 / 10%);
border-radius: 50%;
border-top-color: #0073aa;
animation: wpst-spin 1s ease-in-out infinite;
margin-right: 10px;
vertical-align: middle;
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid rgb(0 0 0 / 10%);
border-radius: 50%;
border-top-color: #0073aa;
animation: wpst-spin 1s ease-in-out infinite;
margin-right: 10px;
vertical-align: middle;
}
@keyframes wpst-spin {
to {
transform: rotate(360deg);
}
to {
transform: rotate(360deg);
}
}
/* Message Styles */
.wpst-modal-message {
padding: 10px;
margin: 10px 0;
border-radius: 3px;
display: none;
padding: 10px;
margin: 10px 0;
border-radius: 3px;
display: none;
}
.wpst-modal-message.success {
background-color: #ecf7ed;
border: 1px solid #46b450;
color: #2a6f31;
background-color: #ecf7ed;
border: 1px solid #46b450;
color: #2a6f31;
}
.wpst-modal-message.error {
background-color: #fbeaea;
border: 1px solid #dc3232;
color: #8a1f1f;
background-color: #fbeaea;
border: 1px solid #dc3232;
color: #8a1f1f;
}

View File

@@ -105,16 +105,10 @@
* @param {string} message Notice message
*/
showNotice: function (type, message) {
const allowedTypes = [ 'success', 'error', 'warning' ];
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 );
const $notice = $( '<div class="wpst-notice ' + type + '"><p>' + message + '</p></div>' );
// Add notice to the page.
$( '.wpst-notices' ).empty().append( $notice );
$( '.wpst-notices' ).html( $notice );
// Automatically remove notice after 5 seconds.
setTimeout(

View File

@@ -153,8 +153,8 @@
showMessage: function (type, message) {
const $message = this.$modal.find( '.wpst-modal-message' );
// Set message as plain text to prevent XSS, then apply type class.
$message.text( message ).removeClass( 'success error' ).addClass( type ).show();
// Set message content and type.
$message.html( message ).removeClass( 'success error' ).addClass( type ).show();
// Hide message after a delay for success messages.
if (type === 'success') {

View File

@@ -12,22 +12,11 @@ if ( ! defined( 'ABSPATH' ) ) {
?>
<!-- Update Source Modal -->
<div
id="wpst-update-source-modal"
class="wpst-modal"
role="dialog"
aria-modal="true"
aria-labelledby="wpst-update-source-modal-title"
tabindex="-1"
>
<div id="wpst-update-source-modal" class="wpst-modal">
<div class="wpst-modal-content">
<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>
<button
type="button"
class="wpst-modal-close"
aria-label="<?php esc_attr_e( 'Close dialog', 'wp-plugin-starter-template' ); ?>"
>&times;</button>
<h2 class="wpst-modal-title"><?php esc_html_e( 'Select Update Source', 'wp-plugin-starter-template' ); ?></h2>
<span class="wpst-modal-close">&times;</span>
</div>
<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/}
download() {
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$1" -o "$2"
elif command -v wget >/dev/null 2>&1; then
wget -qO "$2" "$1"
else
echo "Error: Neither curl nor wget is installed" >&2
exit 1
fi
if command -v curl > /dev/null; then
curl -s "$1" > "$2";
elif command -v wget > /dev/null; then
wget -nv -O "$2" "$1"
fi
}
if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then
WP_BRANCH=${WP_VERSION%\-*}
WP_TESTS_TAG="branches/$WP_BRANCH"
elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then
WP_TESTS_TAG="branches/$WP_VERSION"
elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
@@ -44,38 +40,27 @@ elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
else
# 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
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
echo "Latest WordPress version could not be found"
exit 1
fi
WP_TESTS_TAG="tags/$LATEST_VERSION"
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
install_wp() {
if [ -d "$WP_CORE_DIR" ]; then
return
return;
fi
mkdir -p "$WP_CORE_DIR"
if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
mkdir -p "$WP_CORE_DIR"
download https://wordpress.org/nightly-builds/wordpress-latest.zip "$WP_CORE_DIR/wordpress-nightly.zip"
download https://wordpress.org/nightly-builds/wordpress-latest.zip "$WP_CORE_DIR/wordpress-nightly.zip"
unzip -q "$WP_CORE_DIR/wordpress-nightly.zip" -d "$WP_CORE_DIR"
rm "$WP_CORE_DIR/wordpress-nightly.zip"
else
@@ -86,7 +71,6 @@ install_wp() {
if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
LATEST_VERSION=${WP_VERSION%??}
else
# shellcheck disable=SC2001
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)
fi
@@ -98,7 +82,7 @@ install_wp() {
else
local ARCHIVE_NAME="wordpress-$WP_VERSION"
fi
download https://wordpress.org/"${ARCHIVE_NAME}".tar.gz "$WP_CORE_DIR/wordpress.tar.gz"
download https://wordpress.org/"${ARCHIVE_NAME}".tar.gz "$WP_CORE_DIR/wordpress.tar.gz"
tar --strip-components=1 -zxmf "$WP_CORE_DIR/wordpress.tar.gz" -C "$WP_CORE_DIR"
rm "$WP_CORE_DIR/wordpress.tar.gz"
fi
@@ -117,21 +101,12 @@ install_test_suite() {
# set up testing suite if it doesn't yet exist
if [ ! -d "$WP_TESTS_DIR" ]; then
mkdir -p "$WP_TESTS_DIR"
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
git clone --quiet --depth=1 https://github.com/WordPress/wordpress-develop.git /tmp/wordpress-develop
if [ -d /tmp/wordpress-develop/tests/phpunit/includes ]; then
if ! cp -r /tmp/wordpress-develop/tests/phpunit/includes "$WP_TESTS_DIR/"; then
echo "Error: Failed to copy phpunit includes to $WP_TESTS_DIR" >&2
exit 1
fi
cp -r /tmp/wordpress-develop/tests/phpunit/includes "$WP_TESTS_DIR/"
fi
if [ -d /tmp/wordpress-develop/tests/phpunit/data ]; then
if ! cp -r /tmp/wordpress-develop/tests/phpunit/data "$WP_TESTS_DIR/"; then
echo "Error: Failed to copy phpunit data to $WP_TESTS_DIR" >&2
exit 1
fi
cp -r /tmp/wordpress-develop/tests/phpunit/data "$WP_TESTS_DIR/"
fi
fi
@@ -141,15 +116,15 @@ install_test_suite() {
else
download https://raw.githubusercontent.com/WordPress/wordpress-develop/master/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
fi
WP_CORE_DIR="${WP_CORE_DIR%/}"
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/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|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
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/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/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
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
@@ -157,27 +132,27 @@ install_test_suite() {
install_db() {
if [ "${SKIP_DB_CREATE}" = "true" ]; then
if [ ${SKIP_DB_CREATE} = "true" ]; then
return 0
fi
local PARTS
IFS=':' read -ra PARTS <<<"$DB_HOST"
local DB_HOSTNAME=${PARTS[0]}
local DB_SOCK_OR_PORT=${PARTS[1]}
IFS=':' read -ra PARTS <<< "$DB_HOST"
local DB_HOSTNAME=${PARTS[0]};
local DB_SOCK_OR_PORT=${PARTS[1]};
local EXTRA=""
if [ -n "$DB_HOSTNAME" ]; then
if [ -n "$DB_HOSTNAME" ] ; then
if [[ $DB_SOCK_OR_PORT =~ ^[0-9]+$ ]]; then
EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp"
elif [ -n "$DB_SOCK_OR_PORT" ]; then
elif [ -n "$DB_SOCK_OR_PORT" ] ; then
EXTRA=" --socket=$DB_SOCK_OR_PORT"
elif [ -n "$DB_HOSTNAME" ]; then
elif [ -n "$DB_HOSTNAME" ] ; then
EXTRA=" --host=$DB_HOSTNAME --protocol=tcp"
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

View File

@@ -1,5 +1,4 @@
#!/bin/bash
set -euo pipefail
# WordPress Plugin Build Script
# This script creates a clean build of the plugin for distribution
@@ -40,7 +39,7 @@ composer install --no-dev --optimize-autoloader
# Copy plugin files to build directory
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/"
# Copy directories
@@ -71,7 +70,7 @@ if [ -d "vendor" ]; then
cp -R vendor "$BUILD_DIR/"
fi
# Create ZIP file
# Create ZIP file.
echo "Creating ZIP file..."
cd build || exit 1
zip -r "../$ZIP_FILE" "$PLUGIN_SLUG" -x "*.DS_Store" -x "*.git*" -x "*.github*"
@@ -84,10 +83,10 @@ if [ -f "$ZIP_FILE" ]; then
# Deploy to local WordPress installation if environment variable is set
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"
# Copy files to local WordPress installation

View File

@@ -18,14 +18,6 @@ describe('WordPress Playground Single Site Tests', () => {
cy.visit('/wp-admin/plugins.php', { timeout: 30000 });
cy.get('body', { timeout: 15000 }).then(($body) => {
// Verify the starter template plugin exists and is activated.
if ($body.find('tr[data-slug="wp-plugin-starter-template-for-ai-coding"]').length) {
cy.get('tr[data-slug="wp-plugin-starter-template-for-ai-coding"]').should('exist');
cy.get('tr[data-slug="wp-plugin-starter-template-for-ai-coding"] .deactivate a').should('exist');
} else {
cy.log('Starter template plugin not found by slug, skipping check');
}
if ($body.text().includes('Plugin Toggle')) {
cy.contains('tr', 'Plugin Toggle').should('exist');
cy.contains('tr', 'Plugin Toggle').find('.deactivate').should('exist');

View File

@@ -1,75 +1,30 @@
/* eslint-env mocha, jquery, cypress */
describe( 'WordPress Single Site Tests', () => {
it( 'Can access the site', () => {
cy.visit( '/' );
cy.get( 'body' ).should( 'exist' );
} );
describe('WordPress Single Site Tests', () => {
it('Can access the site', () => {
cy.visit('/');
cy.get('body').should('exist');
});
it( 'Can login to the admin area', () => {
cy.loginAsAdmin();
cy.get( '#wpadminbar' ).should( 'exist' );
cy.get( '#dashboard-widgets' ).should( 'exist' );
} );
it('Can login to the admin area', () => {
cy.loginAsAdmin();
cy.get('#wpadminbar').should('exist');
cy.get('#dashboard-widgets').should('exist');
});
it( 'Plugin is activated', () => {
// Use our custom command to check and activate the plugin if needed.
cy.activatePlugin( 'wp-plugin-starter-template-for-ai-coding' );
it('Plugin is activated', () => {
// Use our custom command to check and activate the plugin if needed
cy.activatePlugin('wp-plugin-starter-template-for-ai-coding');
// Verify it's active.
cy.get( 'tr[data-slug="wp-plugin-starter-template-for-ai-coding"] .deactivate' ).should( 'exist' );
} );
// Verify it's active
cy.get('tr[data-slug="wp-plugin-starter-template-for-ai-coding"] .deactivate').should('exist');
});
it( 'Plugin row is visible on the plugins page', () => {
cy.loginAsAdmin();
cy.visit( '/wp-admin/plugins.php' );
it('Plugin settings page loads correctly', () => {
cy.loginAsAdmin();
// Verify the plugin row exists with the correct slug.
cy.get( 'tr[data-slug="wp-plugin-starter-template-for-ai-coding"]' ).should( 'exist' );
// Navigate to the plugin settings page (if it exists)
cy.visit('/wp-admin/options-general.php?page=wp-plugin-starter-template');
// Verify the plugin name is displayed.
cy.get( 'tr[data-slug="wp-plugin-starter-template-for-ai-coding"] .plugin-title strong' )
.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' );
} );
} );
// This is a basic check - adjust based on your actual plugin's settings page
cy.get('h1').should('contain', 'WP Plugin Starter Template');
});
});

View File

@@ -46,8 +46,8 @@ class Admin {
*/
public function enqueue_admin_assets(): void {
// @phpcs:disable WordPress.Security.NonceVerification.Recommended
// @phpcs:disable WordPress.Security.NonceVerification.Missing
// @phpcs:disable WordPress.Security.NonceVerification.Recommended
// @phpcs:disable WordPress.Security.NonceVerification.Missing
// For production, use filter_input.
$page = '';
if ( defined( 'PHPUNIT_RUNNING' ) && PHPUNIT_RUNNING ) {
@@ -64,17 +64,15 @@ class Admin {
if ( ! $page || 'wp_plugin_starter_template_settings' !== $page ) {
return;
}
// @phpcs:enable
// @phpcs:enable
// Get the plugin version.
$plugin_version = $this->core->get_plugin_version();
$plugin_url = $this->get_plugin_base_url();
// Enqueue styles.
\wp_enqueue_style(
'wpst-admin-styles',
$plugin_url . 'admin/css/admin-styles.css',
plugin_dir_url( dirname( __DIR__ ) ) . 'admin/css/admin-styles.css',
array(), // Dependencies.
$plugin_version // Version.
);
@@ -82,7 +80,7 @@ class Admin {
// Enqueue admin scripts.
\wp_enqueue_script(
'wpst-admin-script',
$plugin_url . 'admin/js/admin-scripts.js',
plugin_dir_url( dirname( __DIR__ ) ) . 'admin/js/admin-scripts.js',
array( 'jquery' ),
$plugin_version, // Version.
true
@@ -101,21 +99,4 @@ class Admin {
$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
* 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.
if ( ! defined( 'ABSPATH' ) ) {

View File

@@ -29,7 +29,6 @@
"test:phpunit:multisite": "WP_MULTISITE=1 composer test",
"build": "./build.sh",
"lint:js": "eslint cypress/",
"lint:css": "stylelint \"**/*.css\" --allow-empty-input",
"lint:php": "composer run-script phpcs",
"lint:php:simple": "composer run-script phpcs:simple",
"lint:phpstan": "composer run-script phpstan",
@@ -39,7 +38,7 @@
"test:php": "composer run-script test",
"lint": "composer run-script lint",
"fix": "composer run-script fix",
"quality": "npm run lint && npm run lint:css && npm run test:php"
"quality": "npm run lint && npm run test:php"
},
"repository": {
"type": "git",
@@ -65,8 +64,6 @@
"@wp-playground/cli": "^3.0.22",
"cypress": "^13.17.0",
"eslint": "^8.57.0",
"eslint-plugin-cypress": "^2.15.1",
"stylelint": "^16.0.0",
"stylelint-config-standard": "^36.0.0"
"eslint-plugin-cypress": "^2.15.1"
}
}

View File

@@ -14,7 +14,7 @@
<exclude-pattern>*/build/*</exclude-pattern>
<exclude-pattern>*/dist/*</exclude-pattern>
<!-- Command line arguments -->
<!-- Command line arguments: combined short flags (-s shows sniff codes, -p shows progress) -->
<arg value="sp"/>
<arg name="extensions" value="php"/>
<arg name="basepath" value="."/>

View File

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

View File

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

View File

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

View File

@@ -150,7 +150,7 @@ When you receive feedback from these code quality tools, you can use AI assistan
1. Copy the output from the code quality tool
2. Paste it into your AI assistant chat
3. Ask the AI to help you understand and resolve the issues
3. Request the AI's assistance to interpret and resolve the reported issues
4. Apply the suggested fixes
5. Commit the changes and verify that the issues are resolved

View File

@@ -46,7 +46,7 @@ if ( getenv( 'WP_PHPUNIT__DIR' ) ) {
// Include plugin files needed for tests.
require_once WPST_PLUGIN_DIR . 'includes/class-core.php';
require_once WPST_PLUGIN_DIR . 'includes/class-plugin.php';
if ( file_exists( WPST_PLUGIN_DIR . 'includes/Admin/class-admin.php' ) ) {
require_once WPST_PLUGIN_DIR . 'includes/Admin/class-admin.php';
if ( file_exists( WPST_PLUGIN_DIR . 'admin/lib/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',
]);
// 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
WP_Mock::userFunction('wp_enqueue_style', [
'times' => 1,
'args' => [
'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',
],
@@ -117,7 +122,7 @@ class AdminTest extends \WP_Mock\Tools\TestCase {
'times' => 1,
'args' => [
'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'],
'1.0.0',
true,

View File

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