Markdown Converter
Agent skill for markdown-converter
**Plugin Name:** ACF Service Management Suite
Sign in to like and favorite skills
Plugin Name: ACF Service Management Suite
Purpose: Complete service business management for WordPress - locations, service areas, and team members
Author: Falls Technology Group, LLC
Developer: Ryan T. M. Reiffenberger (github.com/ReclaimerGold)
License: GPL v2 or later
Support: https://github.com/ReclaimerGold/acf-service-management-suite
Current Version: 2.2.0
filename.old, filename.bak, filename-backup.extensionfilename.extension.old before modifying/workspace/ directory for temporary files, drafts, and experiments/workspace/ (gitignored)⚠️ CRITICAL: ALL code changes MUST include proper version management following Conventional Commits.
BEFORE making ANY code changes:
Required Version Update Files (MUST ALL BE UPDATED TOGETHER):
acf-location-shortcodes.php - Plugin header Version: AND ACF_LS_VERSION constantREADME.md - Version badgeDEVELOP.md - Version and Last Updated datecopilot-instructions.md - Current VersionCHANGELOG.md - New version entry with changesVersion Determination:
feat: → MINOR version bump (2.0.0 → 2.1.0) feat!: → MAJOR version bump (2.0.0 → 3.0.0) fix: → PATCH version bump (2.0.0 → 2.0.1) docs: → NO version bump (documentation only) style: → NO version bump (formatting only) refactor: → NO version bump (unless behavior changes) perf: → PATCH version bump (2.0.0 → 2.0.1) test: → NO version bump (tests only) chore: → NO version bump (unless dependencies change)
Example Version Update Workflow:
# 1. User requests: "Add team member filtering to Elementor" # 2. Determine: feat: (new feature) → MINOR bump (2.1.0 → 2.2.0) # 3. Update ALL version files: # - acf-location-shortcodes.php: Version: 2.2.0 + ACF_LS_VERSION = '2.2.0' # - README.md: Version-2.2.0-green # - DEVELOP.md: Version: 2.2.0, Last Updated: [current date] # - copilot-instructions.md: Current Version: 2.2.0 # - CHANGELOG.md: Add [2.2.0] - [date] section # 4. Make code changes # 5. Commit: "feat: add hierarchical location support for Elementor filters"
CHANGELOG.md Format:
## [2.2.0] - 2025-10-31 ### Added - Hierarchical location support in Elementor filters - Team member filtering by parent location for service areas ### Changed - Filter dropdown now shows only physical locations - Automatic parent detection for service area pages ### Fixed - Team member queries on service area pages
All commit messages MUST follow the Conventional Commits specification:
Format:
<type>[optional scope]: <description> [optional body] [optional footer(s)]
Types:
feat: - New feature for the user (MINOR version bump)fix: - Bug fix for the user (PATCH version bump)docs: - Documentation only changes (NO version bump)style: - Code style changes (formatting, missing semi-colons, etc.) (NO version bump)refactor: - Code change that neither fixes a bug nor adds a feature (NO version bump)perf: - Performance improvement (PATCH version bump)test: - Adding or updating tests (NO version bump)build: - Changes to build system or dependencies (NO version bump)ci: - Changes to CI configuration files and scripts (NO version bump)chore: - Other changes that don't modify src or test files (NO version bump)revert: - Reverts a previous commit (depends on reverted change)Scopes (optional but recommended):
feat(shortcodes): add location_address shortcode fix(elementor): correct query filter for service areas docs(readme): update installation instructions perf(cache): implement object caching for communities
Examples:
# MINOR version bump (2.0.0 → 2.1.0) feat: add automatic parent location detection feat(elementor): add team member filtering by location # PATCH version bump (2.0.0 → 2.0.1) fix: resolve cache invalidation on post update fix(shortcodes): handle missing ACF fields gracefully perf(queries): optimize location hierarchy lookups # NO version bump docs: update README with Elementor usage examples docs(develop): add contribution guidelines style: format code to WordPress standards refactor: extract validation into helper method test: add unit tests for ACF helpers chore: update .gitignore patterns
Breaking Changes (MAJOR version bump): Use
! after type or add BREAKING CHANGE: in footer:
# MAJOR version bump (2.0.0 → 3.0.0) feat!: rebrand to ACF Service Management Suite refactor!: change text domain from acf-location-shortcodes to acf-sms BREAKING CHANGE: Text domain changed, existing translations need update
When user requests code changes:
Version Consistency Check: Before completing any task, verify ALL these match:
Version:ACF_LS_VERSION constantIf versions are inconsistent: Stop and fix ALL files before proceeding.
Creating a release:
# After committing changes with proper version bumps: git tag v2.1.0 git push origin v2.1.0 # GitHub Actions will automatically: # - Run tests # - Build release ZIP # - Create GitHub Release # - Generate release notes from CHANGELOG
Tag Format:
vMAJOR.MINOR.PATCH (e.g., v2.1.0)
NEVER create tags without updating versions first.
// ALWAYS check capabilities before sensitive operations if ( ! current_user_can( 'manage_options' ) ) { wp_die( __( 'Insufficient permissions', 'acf-sms' ) ); } // ALWAYS sanitize input $location_id = absint( $_POST['location_id'] ); $field_name = sanitize_text_field( $_POST['field'] ); // ALWAYS escape output echo esc_html( $title ); echo esc_attr( $class ); echo esc_url( $link ); // ALWAYS verify nonces for forms/AJAX check_ajax_referer( 'acf_sms_action', 'nonce' );
// Use WordPress object cache $cached = wp_cache_get( $key, 'acf_sms_locations' ); if ( false === $cached ) { $cached = expensive_operation(); wp_cache_set( $key, $cached, 'acf_sms_locations', HOUR_IN_SECONDS ); } // Lazy load integrations if ( did_action( 'elementor/loaded' ) ) { // Only load Elementor integration if needed } // Minimize database queries // Cache ACF field checks // Use transients for expensive admin checks
edit_posts capabilityRequired:
Optional:
Included:
location, team-member)acf-service-management-suite/ ├── acf-service-management-suite.php # Bootstrap (constants, singleton, hooks) ├── includes/ │ ├── class-acf-helpers.php # ACF data retrieval + caching │ ├── class-shortcodes.php # All shortcode handlers │ └── class-elementor-integration.php # Elementor query filters ├── assets/ │ ├── css/shortcodes.css # Minimal frontend styles │ └── js/elementor-controls.js # Elementor editor controls ├── .github/ │ └── workflows/ │ ├── test.yml # Automated testing (on push) │ └── release.yml # Automated releases (on tag) ├── workspace/ # GITIGNORED - temp files, drafts, notes │ └── README.md # Explains workspace usage ├── acf-export-2025-10-28.json # Field structure (import ready) ├── README.md # PRIMARY documentation (setup + usage) ├── DEVELOP.md # Extension/contribution guide ├── CHANGELOG.md # Version history ├── copilot-instructions.md # This file ├── .gitignore # Excludes workspace/, *.old, *.bak └── .gitattributes # Export exclusions (dev files)
NO OTHER .md FILES ALLOWED - Consolidate or delete. NO BACKUP FILES (.old, .bak, -copy) ALLOWED - Use workspace/ or Git history.
- Runs on every push/PR:.github/workflows/test.yml
- Runs on version tag push:.github/workflows/release.yml
ACF_Location_Shortcodesclass ACF_Location_Shortcodes { private static $instance = null; public $shortcodes; public $acf_helpers; public $elementor; public static function instance() { } private function __construct() { } private function init_hooks() { } public function check_dependencies() { } public function init() { } private function includes() { } public static function log( $message, $data = array(), $level = 'info' ) { } }
ACF_Location_Shortcodes_ACF_Helpersget_surrounding_communities( $post_id ) - Get + cache communitiesparse_comma_list( $string ) - Parse CSV to arrayis_physical_location( $post_id ) - Check if has addressget_servicing_location( $post_id ) - Get parent locationget_location_field( $field, $post_id, $default ) - Generic getterfield_exists( $field_name, $post_id ) - Validation (v1.1.0+)get_field_names( $post_id ) - List all fields (v1.1.0+)clear_cache( $post_id ) - Manual cache clearACF_Location_Shortcodes_Shortcodes[location_communities] - Display surrounding communities[location_info field="..."] - Display any ACF field[location_list] - Location directory (two modes)[location_address] - Physical address (auto parent lookup)render_error( $message, $debug_data = array() ) - Error displayACF_Location_Shortcodes_Elementorelementor/element/before_section_end (add controls)elementor/query/query_args (filter queries)⚠️ CRITICAL: Always reference the master ACF template file for field structure.
Master Reference:
/acf-import-templates/acf-template.json
This file contains the complete, authoritative definition of:
When working with ACF fields:
acf-template.json for the exact field nameField Name Verification:
# Example: Finding a field in the template grep -A 5 '"name": "field_name"' acf-import-templates/acf-template.json
Defined in:
acf-import-templates/acf-template.json → Field Group: "Location Details"
service_area_shorthand (Text) - "Sioux Falls, SD"address (Text) - Physical address (empty = service area)phone_number (Text) - Contact numberlocation_site_url (URL) - Dedicated siteservicing_physical_location (Post Object) - Parent locationfull_service_area_name (Text) - "Greater Sioux Falls"located_near (Text) - "Near Falls Park"metro_area_name (Text) - "Sioux Falls Metro"county_name (Text) - "Minnehaha County"surrounding_community_list (Text) - CSV communitiesteam_members_assigned (Relationship) - Team membersDefined in:
acf-import-templates/acf-template.json → Field Group: "Team Member Details"
profile_picture (Image) - Photo (return format: array)title (Text) - Job titlelocation (Post Object, Multiple) - Assigned locations (return format: object)full_profile_url (URL) - Full bio linkLocation Type Logic:
address = Physical Locationaddress = Service Areapublic function render_shortcode_name( $atts ) { // Parse attributes with defaults $atts = shortcode_atts( array( 'location_id' => get_the_ID(), 'field' => '', 'default' => '', 'class' => '', ), $atts, 'shortcode_name' ); // Sanitize inputs $location_id = absint( $atts['location_id'] ); $field = sanitize_text_field( $atts['field'] ); $class = sanitize_html_class( $atts['class'] ); // Validate if ( empty( $location_id ) || 'location' !== get_post_type( $location_id ) ) { return $this->render_error( sprintf( __( 'Invalid location ID: %d', 'acf-sms' ), $location_id ) ); } // Get data with error handling $result = $this->acf_helpers->get_location_field_validated( $field, $location_id ); if ( ! $result['success'] ) { return $this->render_error( $result['error'], $result['debug'] ); } // Build output ob_start(); ?> <div class="acf-sms-<?php echo esc_attr( $class ); ?>"> <?php echo esc_html( $result['data'] ); ?> </div> <?php return ob_get_clean(); }
private function render_error( $message, $debug_data = array() ) { // Basic error for all users $output = sprintf( '<div class="acf-sms-error"><p>%s</p></div>', esc_html( $message ) ); // Debug data only for editors when debug enabled if ( defined( 'ACF_LS_DEBUG' ) && ACF_LS_DEBUG && current_user_can( 'edit_posts' ) && ! empty( $debug_data ) ) { $output .= sprintf( '<details class="acf-sms-debug"><summary>Debug Info</summary><pre>%s</pre></details>', esc_html( print_r( $debug_data, true ) ) ); } return $output; }
ACF_Location_Shortcodes::log( 'Field validation failed', array( 'field' => $field_name, 'post_id' => $post_id, 'available_fields' => $available, ), 'warning' // or 'info', 'error' );
$cache_key = 'communities_' . $post_id; $cached = wp_cache_get( $cache_key, 'acf_sms_locations' ); if ( false !== $cached ) { ACF_Location_Shortcodes::log( 'Cache hit', array( 'key' => $cache_key ), 'info' ); return $cached; } // Expensive operation $data = $this->get_expensive_data( $post_id ); wp_cache_set( $cache_key, $data, 'acf_sms_locations', HOUR_IN_SECONDS ); ACF_Location_Shortcodes::log( 'Cache miss', array( 'key' => $cache_key ), 'info' ); return $data;
When bumping versions, update in this exact order:
CHANGELOG.md - Add new version section FIRST
## [2.0.0] - 2025-10-28 ### Added - Complete post type structure included ### Changed - Rebranded to ACF Service Management Suite
acf-service-management-suite.php - Plugin header + constant
* Version: 2.2.0 define( 'ACF_LS_VERSION', '2.2.0' );
README.md - Badge + Credits section
[] **Version:** 2.2.0 **Last Updated:** October 31, 2025
copilot-instructions.md - Current Version field
**Current Version:** 2.2.0
Version bumps trigger based on commit types:
feat: commits → Increment MINOR version (2.1.0 → 2.2.0)fix: commits → Increment PATCH version (2.1.0 → 2.1.1)feat!: or BREAKING CHANGE: → Increment MAJOR version (2.0.0 → 3.0.0)docs:, style:, refactor:, perf:, test:, chore: → No version bump (unless bundled with feat/fix)Example Version Flow:
# Current: 2.2.0 git commit -m "fix: resolve cache invalidation bug" # Triggers: 2.1.0 → 2.1.1 git commit -m "feat: add location_distance shortcode" # Triggers: 2.1.1 → 2.2.0 git commit -m "feat!: change shortcode attribute names for consistency" # Triggers: 2.1.0 → 3.0.0
Pre-Release Checklist:
git tag -a v2.0.0 -m "chore: release v2.0.0"git tag -a v2.0.0 -m "chore(release): version 2.0.0"Semantic Versioning:
Commit Message for Version Bumps:
# When releasing a new version git commit -m "chore(release): bump version to 2.1.0 - Updated version in plugin header - Updated ACF_LS_VERSION constant - Updated README.md version badge - Updated copilot-instructions.md" git tag -a v2.1.0 -m "chore(release): version 2.1.0"
2. **acf-service-management-suite.php** - Plugin header + constant ```php * Version: 2.2.0 define( 'ACF_LS_VERSION', '2.2.0' );
README.md - Badge + Credits section
[] **Version:** 2.2.0 **Last Updated:** October 31, 2025
copilot-instructions.md - Current Version field
**Current Version:** 2.2.0
Pre-Release Checklist:
git tag -a v2.2.0 -m "Version 2.2.0"Semantic Versioning:
FORBIDDEN - Never Create Backup Files:
# ❌ WRONG - Do not do this mv file.php file.php.old cp file.php file.php.backup mv file.php file-old.php
CORRECT - Use Workspace or Git:
# ✅ CORRECT - For temporary preservation cp file.php workspace/file-draft.php mv draft.md workspace/planning-notes.md # ✅ CORRECT - For file history git log file.php git show HEAD~1:file.php git diff HEAD~1 file.php # ✅ CORRECT - For complete removal rm obsolete-file.md # Git history preserves it
Workspace Directory Usage:
/workspace/ is gitignored - safe for local filesif ( ! function_exists( 'get_field' ) )get_field()__(), _e(), esc_html__(), etc.php -l filename.php# 1. Write code for one feature/fix # 2. Test thoroughly # 3. Stage changes git add includes/class-shortcodes.php # 4. Commit with conventional format git commit -m "feat(shortcodes): add location_distance shortcode - Calculate distance between two locations - Support multiple distance units (miles/km) - Add caching for distance calculations" # 5. If this triggers version bump (feat/fix/breaking) # Update CHANGELOG.md, version numbers, then: git add CHANGELOG.md acf-service-management-suite.php README.md copilot-instructions.md git commit -m "chore(release): bump version to 2.1.0" git tag -a v2.1.0 -m "chore(release): version 2.1.0"
# Plugin Name Badges, brief description ## Why Use This Plugin? Bullet points only ## Features Concise feature list with code examples ## Requirements Min/recommended versions ## Installation 3 methods max (WordPress Admin, FTP, Git) ## Quick Start Guide 5 steps to first working shortcode ## Documentation Shortcode reference tables inline (no separate files) ## Usage Examples Real-world scenarios with code ## ACF Field Schema Complete field reference + import instructions ## Troubleshooting Common issues with solutions (no fluff) ## Support GitHub issues link only ## License Full GPL v2 text ## Credits Author, company, version, date
# Development Guide - Local environment setup - Plugin architecture - Coding standards - Extension patterns (how to add shortcodes, fields, integrations) - Testing procedures - Contribution workflow - Release process
# Changelog ## [Unreleased] ## [2.0.0] - 2025-10-28 ### Added ### Changed ### Fixed ### Security
/* Block */ .acf-sms-communities { } /* Element */ .acf-sms-communities__item { } .acf-sms-communities__emoji { } .acf-sms-communities__text { } /* Modifier */ .acf-sms-communities--vertical { } .acf-sms-communities--grid { }
!important unless absolutely necessary$_GET, $_POST, $_REQUEST sanitizedshortcode_atts()absint() and post type checkesc_html() for textesc_attr() for attributesesc_url() for URLswp_kses_post() for HTML contentmanage_options or similarinstall_plugins + activate_pluginsedit_posts minimumwp_create_nonce() and check_ajax_referer()Required Stack:
Test Structure:
tests/ ├── bootstrap.php # Test suite bootstrap ├── TestCase.php # Base test case with helpers ├── README.md # Complete testing documentation ├── mocks/ │ └── wordpress-functions.php # WordPress function mocks ├── Unit/ │ └── [ClassName]Test.php # One test file per class └── Integration/ └── [Feature]Test.php # Integration tests
Files:
[ClassName]Test.php
ACFHelpersTest.php, ShortcodesTest.php, MultisiteSyncTest.phpClasses:
[ClassName]Test extends TestCase
ACF_SMS\Tests\Unit or ACF_SMS\Tests\IntegrationMethods:
test_[method]_[scenario]_[expected]
test_parse_comma_list_with_valid_input()test_get_location_field_returns_default_when_empty()test_shortcode_renders_error_for_invalid_post_type()CRITICAL: Follow Test-Driven Development for ALL new features and bug fixes.
Step 1: Write Failing Test First
public function test_new_shortcode_renders_phone_number() { // Arrange $this->acf_helpers->shouldReceive( 'get_location_field' ) ->with( 'phone_number', 123, '' ) ->andReturn( '(605) 555-1234' ); // Act $result = $this->shortcodes->location_phone( array() ); // Assert $this->assertStringContainsString( '(605) 555-1234', $result ); $this->assertNotContainsError( $result ); }
Step 2: Run Test - Verify It Fails
./vendor/bin/phpunit --filter test_new_shortcode # Expected: FAIL (method doesn't exist)
Step 3: Implement Minimum Code
public function location_phone( $atts ) { $phone = $this->acf_helpers->get_location_field( 'phone_number', 123, '' ); return sprintf( '<span>%s</span>', esc_html( $phone ) ); }
Step 4: Run Test - Verify It Passes
./vendor/bin/phpunit --filter test_new_shortcode # Expected: PASS
Step 5: Add Edge Case Tests
public function test_new_shortcode_shows_error_when_no_phone() { } public function test_new_shortcode_uses_custom_class() { } public function test_new_shortcode_handles_invalid_location_id() { }
Every public method MUST have tests for:
Example: Comprehensive Method Testing
// Test suite for parse_comma_list() public function test_parse_comma_list_with_valid_input() { } public function test_parse_comma_list_with_extra_spaces() { } public function test_parse_comma_list_with_empty_string() { } public function test_parse_comma_list_with_single_item() { } public function test_parse_comma_list_filters_empty_values() { } public function test_parse_comma_list_with_null_input() { }
Critical Classes (90%+ coverage required):
ACF_Location_Shortcodes_ACF_Helpers - Core data layerACF_Location_Shortcodes_Shortcodes - All shortcodesACF_Location_Shortcodes_Multisite_Sync - Sync logic + mediaImportant Classes (75%+ coverage required):
ACF_Location_Shortcodes_Elementor - Query filteringACF_Location_Shortcodes_Admin - Admin interfaceACF_Location_Shortcodes_Network_Admin - Network settingsRun coverage report:
composer test:coverage # Generates coverage/index.html
WordPress Functions (Brain Monkey):
use Brain\Monkey\Functions; // Simple return Functions\when( 'get_option' )->justReturn( 'value' ); // Return argument unchanged (escaping functions) Functions\when( 'esc_html' )->returnArg(); // Expect exact call Functions\expect( 'update_option' ) ->once() ->with( 'key', 'value' ) ->andReturn( true ); // Conditional logic Functions\when( 'get_post_type' )->alias( function( $id ) { return $id === 123 ? 'location' : 'post'; } );
ACF Functions:
// Mock ACF helpers class $this->acf_helpers = Mockery::mock( 'ACF_Location_Shortcodes_ACF_Helpers' ); $this->acf_helpers->shouldReceive( 'get_location_field' ) ->once() ->with( 'phone_number', 123, '' ) ->andReturn( '555-1234' );
Provided by base TestCase:
// Check HTML content $this->assertStringContainsHTML( '<div class="test">', $output ); // Check for error display $this->assertContainsError( $output ); $this->assertNotContainsError( $output ); // Mock post objects $post = $this->createMockPost( array( 'ID' => 123, 'post_type' => 'location', ) ); // Mock ACF fields $field = $this->createMockACFField( 'phone', 'text', '555-1234' );
All tests:
composer test # or ./vendor/bin/phpunit
Unit tests only:
composer test:unit
Integration tests only:
composer test:integration
Specific test file:
./vendor/bin/phpunit tests/Unit/ACFHelpersTest.php
Specific test method:
./vendor/bin/phpunit --filter test_parse_comma_list
With coverage:
composer test:coverage
Before ANY commit with code changes:
composer testTest-related commits:
# Adding new tests (NO version bump) git commit -m "test: add unit tests for media sync functionality" # Updating tests (NO version bump) git commit -m "test: update ACF helpers tests for new validation" # Fixing tests (NO version bump) git commit -m "test: fix failing integration test for multisite sync"
Version Impact:
test: commits do NOT trigger version bumps.
DO ✅
DON'T ❌
When adding a new feature:
When fixing a bug:
When updating existing code:
Response Format:
I'll add tests for [feature] following TDD: 1. Created failing test: tests/Unit/ClassTest.php::test_method_scenario() 2. Test output: FAIL (expected) 3. Implemented: includes/class-name.php::method() 4. Test output: PASS 5. Added edge cases: test_method_with_null(), test_method_with_empty() 6. Full test suite: 124 tests, 124 passed ✓ 7. Code coverage: 87% (↑ from 85%) Ready to commit with: test: add comprehensive tests for [feature]
Complete testing guide:
tests/README.md
function_exists('get_field')cp file.php workspace/file-backup-YYYY-MM-DD.phpLast Updated: October 28, 2025
Current Version: 2.0.0
Primary Developer: Ryan T. M. Reiffenberger
Company: Falls Technology Group, LLC