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 - name: Install dependencies
run: composer install --prefer-dist --no-progress 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 - name: Run PHPCS
run: composer phpcs run: composer phpcs
continue-on-error: true continue-on-error: true
@@ -61,9 +56,6 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: composer install --prefer-dist --no-progress run: composer install --prefer-dist --no-progress
- name: Install PHPStan WordPress stubs
run: composer require --dev szepeviktor/phpstan-wordpress
- name: Run PHPStan - name: Run PHPStan
run: composer phpstan run: composer phpstan
continue-on-error: true continue-on-error: true

View File

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

View File

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

View File

@@ -67,7 +67,7 @@ if ($condition) {
### Documentation ### Documentation
* All classes, methods, and functions should be documented using PHPDoc * 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 ```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** 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 * 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 * 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: * Example prompt:
```text ```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: 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
@@ -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 1. Copy the output from the code quality tool
2. Paste it into your AI assistant chat 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 4. Apply the suggested fixes
5. Commit the changes and verify that the issues are resolved 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 JavaScript coding standards: `npm run lint:js`
* Check CSS coding standards: `npm run lint:css` * 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 ## 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 1. Copy the output from the code quality tool
2. Paste it into your AI assistant chat 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 4. Apply the suggested fixes
5. Commit the changes and verify that the issues are resolved 5. Commit the changes and verify that the issues are resolved

View File

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

View File

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

View File

@@ -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(

View File

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

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 fi
echo "Error: Neither curl nor wget is installed" >&2
exit 1
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,38 +40,27 @@ 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"
if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
mkdir -p "$WP_CORE_DIR" 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" unzip -q "$WP_CORE_DIR/wordpress-nightly.zip" -d "$WP_CORE_DIR"
rm "$WP_CORE_DIR/wordpress-nightly.zip" rm "$WP_CORE_DIR/wordpress-nightly.zip"
else else
@@ -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
@@ -98,7 +82,7 @@ install_wp() {
else else
local ARCHIVE_NAME="wordpress-$WP_VERSION" local ARCHIVE_NAME="wordpress-$WP_VERSION"
fi 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" tar --strip-components=1 -zxmf "$WP_CORE_DIR/wordpress.tar.gz" -C "$WP_CORE_DIR"
rm "$WP_CORE_DIR/wordpress.tar.gz" rm "$WP_CORE_DIR/wordpress.tar.gz"
fi fi
@@ -117,21 +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"
if ! git clone --quiet --depth=1 --branch "$GIT_REF" https://github.com/WordPress/wordpress-develop.git /tmp/wordpress-develop; then git clone --quiet --depth=1 https://github.com/WordPress/wordpress-develop.git /tmp/wordpress-develop
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
@@ -141,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
@@ -157,27 +132,27 @@ 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
if [[ $DB_SOCK_OR_PORT =~ ^[0-9]+$ ]]; then if [[ $DB_SOCK_OR_PORT =~ ^[0-9]+$ ]]; then
EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" 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" EXTRA=" --socket=$DB_SOCK_OR_PORT"
elif [ -n "$DB_HOSTNAME" ]; then elif [ -n "$DB_HOSTNAME" ] ; then
EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" EXTRA=" --host=$DB_HOSTNAME --protocol=tcp"
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

@@ -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,7 +39,7 @@ 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
@@ -71,7 +70,7 @@ if [ -d "vendor" ]; then
cp -R vendor "$BUILD_DIR/" cp -R vendor "$BUILD_DIR/"
fi 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*"
@@ -84,10 +83,10 @@ if [ -f "$ZIP_FILE" ]; then
# 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..." 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"
# Copy files to local WordPress installation # 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.visit('/wp-admin/plugins.php', { timeout: 30000 });
cy.get('body', { timeout: 15000 }).then(($body) => { 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')) { if ($body.text().includes('Plugin Toggle')) {
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');

View File

@@ -1,75 +1,30 @@
/* 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( '/' ); cy.get('body').should('exist');
cy.get( 'body' ).should( 'exist' ); });
} );
it( 'Can login to the admin area', () => { it('Can login to the admin area', () => {
cy.loginAsAdmin(); cy.loginAsAdmin();
cy.get( '#wpadminbar' ).should( 'exist' ); cy.get('#wpadminbar').should('exist');
cy.get( '#dashboard-widgets' ).should( 'exist' ); cy.get('#dashboard-widgets').should('exist');
} ); });
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

@@ -46,8 +46,8 @@ class Admin {
*/ */
public function enqueue_admin_assets(): void { public function enqueue_admin_assets(): void {
// @phpcs:disable WordPress.Security.NonceVerification.Recommended // @phpcs:disable WordPress.Security.NonceVerification.Recommended
// @phpcs:disable WordPress.Security.NonceVerification.Missing // @phpcs:disable WordPress.Security.NonceVerification.Missing
// For production, use filter_input. // For production, use filter_input.
$page = ''; $page = '';
if ( defined( 'PHPUNIT_RUNNING' ) && PHPUNIT_RUNNING ) { if ( defined( 'PHPUNIT_RUNNING' ) && PHPUNIT_RUNNING ) {
@@ -64,17 +64,15 @@ class Admin {
if ( ! $page || 'wp_plugin_starter_template_settings' !== $page ) { if ( ! $page || 'wp_plugin_starter_template_settings' !== $page ) {
return; return;
} }
// @phpcs:enable // @phpcs:enable
// 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' ) ) {

View File

@@ -29,7 +29,6 @@
"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/", "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",
@@ -39,7 +38,7 @@
"test:php": "composer run-script test", "test:php": "composer run-script test",
"lint": "composer run-script lint", "lint": "composer run-script lint",
"fix": "composer run-script fix", "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": { "repository": {
"type": "git", "type": "git",
@@ -65,8 +64,6 @@
"@wp-playground/cli": "^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": "^2.15.1", "eslint-plugin-cypress": "^2.15.1"
"stylelint": "^16.0.0",
"stylelint-config-standard": "^36.0.0"
} }
} }

View File

@@ -14,7 +14,7 @@
<exclude-pattern>*/build/*</exclude-pattern> <exclude-pattern>*/build/*</exclude-pattern>
<exclude-pattern>*/dist/*</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 value="sp"/>
<arg name="extensions" value="php"/> <arg name="extensions" value="php"/>
<arg name="basepath" value="."/> <arg name="basepath" value="."/>

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

@@ -68,7 +68,7 @@
"landingPage": "/wp-admin/", "landingPage": "/wp-admin/",
"login": true, "login": true,
"features": { "features": {
"networking": false "networking": true
}, },
"steps": [ "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 1. Copy the output from the code quality tool
2. Paste it into your AI assistant chat 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 4. Apply the suggested fixes
5. Commit the changes and verify that the issues are resolved 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. // 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 ) ) {