Skip to content

Latest commit

 

History

History
609 lines (460 loc) · 18.1 KB

File metadata and controls

609 lines (460 loc) · 18.1 KB

Development Guide

This guide covers setting up your development environment, running tests, and contributing to Sybgo.

Development Setup

Requirements

  • WordPress 5.0+
  • PHP 7.4+
  • Composer
  • MySQL 5.7+ or MariaDB 10.2+

Local Environment Options

  • Local by Flywheel (recommended)
  • MAMP/WAMP/XAMPP
  • Docker (wp-env, Lando, etc.)

Installation

# 1. Clone repository
cd /path/to/wordpress/wp-content/plugins
git clone <repo-url> sybgo
cd sybgo

# 2. Install dependencies
composer install

# 3. Activate in WordPress
# WP Admin → Plugins → Activate "Sybgo"

# 4. Verify installation
wp plugin list | grep sybgo

Development Dependencies

# Code standards
composer require --dev squizlabs/php_codesniffer
composer require --dev wp-coding-standards/wpcs

# Testing
composer require --dev phpunit/phpunit
composer require --dev brain/monkey
composer require --dev mockery/mockery

Code Standards

Sybgo follows WordPress Coding Standards and group.one technical standards.

Check Code

# Check all PHP files
composer phpcs

# Check specific file
composer phpcs -- database/class-event-repository.php

Auto-Fix Issues

# Fix all fixable issues
composer phpcs:fix

# Fix specific file
composer phpcs:fix -- database/class-event-repository.php

Common Standards

Type Declarations:

declare(strict_types=1);

Namespacing:

namespace Rocket\Sybgo\Events\Trackers;

Security:

// Nonces
wp_nonce_field( 'sybgo_action', 'sybgo_nonce' );
check_admin_referer( 'sybgo_action', 'sybgo_nonce' );

// Output escaping
echo esc_html( $text );
echo esc_url( $url );
echo esc_attr( $attr );

// Input sanitization
$clean = sanitize_text_field( $_POST['field'] );
$email = sanitize_email( $_POST['email'] );

// SQL preparation
$wpdb->prepare( "SELECT * FROM table WHERE id = %d", $id );

Testing

See CLAUDE.md at the repo root for the exact commands to run unit tests, PHPCS, and PHPStan across lib/ and wp-plugin/, including the symlink fix for PHPStan.

Writing Tests

Unit Test Example:

namespace Rocket\Sybgo\Tests\Unit\Events;

use Brain\Monkey;
use Brain\Monkey\Functions;
use Mockery;
use PHPUnit\Framework\TestCase;
use Rocket\Sybgo\Database\Event_Repository;
use Rocket\Sybgo\Events\Trackers\Post_Tracker;

class PostTrackerTest extends TestCase {

    protected function setUp(): void {
        parent::setUp();
        Monkey\setUp();

        // Mock WordPress functions
        Functions\when( 'get_current_user_id' )->justReturn( 1 );
        Functions\when( 'get_permalink' )->alias( function( $id ) {
            return "https://example.com/post-{$id}";
        } );
    }

    protected function tearDown(): void {
        Monkey\tearDown();
        Mockery::close();
        parent::tearDown();
    }

    public function test_track_post_publish() {
        $event_repo = Mockery::mock( Event_Repository::class );
        $event_repo->shouldReceive( 'create' )
            ->once()
            ->with( Mockery::type( 'array' ) )
            ->andReturn( 123 );

        $tracker = new Post_Tracker( $event_repo );

        // Test logic here
        $this->assertTrue( true );
    }
}

Testing Checklist

When adding new features:

  • Write unit tests for all new classes
  • Mock WordPress functions with Brain\Monkey
  • Test both success and error cases
  • Ensure 100% pass rate before committing
  • Run code standards check

Development Workflow

Making Changes

# 1. Create feature branch
git checkout -b feature/my-feature

# 2. Make changes
# Edit files...

# 3. Check code standards
composer phpcs

# 4. Fix any issues
composer phpcs:fix

# 5. Run tests
composer run-tests

# 6. Commit changes
git add .
git commit -m "Add feature: description"

# 7. Push and create PR
git push origin feature/my-feature

Debugging

Enable WordPress Debug Mode:

// In wp-config.php
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );

Add Debug Logging:

error_log( 'Sybgo: Event tracked - ' . $event_type );
error_log( print_r( $event_data, true ) );

Check Debug Log:

tail -f wp-content/debug.log

Database Inspection

# View recent events
wp db query "SELECT * FROM wp_sybgo_events ORDER BY event_timestamp DESC LIMIT 10"

# Pretty print JSON
wp db query "SELECT id, event_type, JSON_PRETTY(event_data) FROM wp_sybgo_events LIMIT 1"

# Count events by type
wp db query "SELECT event_type, COUNT(*) as total FROM wp_sybgo_events GROUP BY event_type"

# View reports
wp db query "SELECT * FROM wp_sybgo_reports ORDER BY period_end DESC"

Manual Testing

Test Event Tracking

# Publish a post
wp post create --post_title="Test Post" --post_status=publish

# Verify event created
wp db query "SELECT * FROM wp_sybgo_events WHERE event_type='post_published' ORDER BY created_at DESC LIMIT 1"

# Edit the post
wp post update 1 --post_content="Updated content"

# Check edit event
wp db query "SELECT event_type, JSON_EXTRACT(event_data, '$.metadata.edit_magnitude') FROM wp_sybgo_events WHERE event_type='post_edited' ORDER BY created_at DESC LIMIT 1"

Test Report Freezing

# Manual freeze
wp cron event run sybgo_freeze_weekly_report

# Verify report frozen
wp db query "SELECT * FROM wp_sybgo_reports WHERE status='frozen' ORDER BY period_end DESC LIMIT 1"

# Check events assigned
wp db query "SELECT COUNT(*) FROM wp_sybgo_events WHERE report_id IS NOT NULL"

Test Email Sending

# Manual send
wp cron event run sybgo_send_report_emails

# Check email log
wp db query "SELECT * FROM wp_sybgo_email_log ORDER BY created_at DESC LIMIT 5"

# View email content (from log)
wp db query "SELECT recipient, status, error_message FROM wp_sybgo_email_log WHERE status='failed'"

Project Structure

sybgo/
├── sybgo.php                     # Main plugin file (WordPress header)
├── class-sybgo.php               # Lifecycle orchestrator (activate, deactivate, init)
├── class-ability-manager.php     # WP7 Ability API registration utility
├── class-cron-manager.php        # WP-Cron registration utility
├── class-factory.php             # Dependency injection
│
├── modules/                      # Feature modules (one per domain area)
│   ├── interface-module.php      # Module_Interface contract
│   ├── class-event-module.php    # Event tracking wiring
│   ├── class-report-module.php   # Reporting UI and freeze cron
│   ├── class-email-module.php    # Email delivery crons
│   ├── class-ai-module.php       # AI ability registration
│   └── class-settings-module.php # Settings UI, cleanup cron
│
├── database/                     # Data layer
│   ├── class-databasemanager.php
│   ├── class-event-repository.php
│   └── class-report-repository.php
│
├── events/                       # Event tracking
│   ├── class-event-tracker.php
│   ├── class-event-registry.php
│   └── trackers/
│       ├── class-post-tracker.php
│       ├── class-user-tracker.php
│       ├── class-comment-tracker.php
│       └── class-update-tracker.php
│
├── reports/                      # Report generation
│   ├── class-report-manager.php
│   └── class-report-generator.php
│
├── admin/                        # WordPress admin
│   ├── class-admin-manager.php   # Admin registration utility
│   ├── class-dashboard-widget.php
│   ├── class-settings-page.php
│   ├── class-reports-page.php
│   └── class-uninstaller.php     # Plugin cleanup on uninstall
│
├── uninstall.php                 # WP uninstall entry point
│
├── email/                        # Email system
│   ├── class-email-manager.php
│   └── class-email-template.php
│
├── ai/                           # AI integration
│   └── class-ai-summarizer.php
│
├── api/                          # Extensibility
│   └── functions.php
│
├── docs/                         # Documentation
│   ├── event-tracking.md
│   ├── report-lifecycle.md
│   ├── extension-api.md
│   └── development.md (this file)
│
└── Tests/                        # Test suite
    ├── Unit/
    │   ├── Events/
    │   ├── Reports/
    │   ├── Email/
    │   └── Admin/
    └── Integration/

Feature Module Architecture

New domain wiring goes into a feature module under wp-plugin/modules/, not directly into class-sybgo.php. Each module implements Module_Interface (a single boot(): void method) and receives only the dependencies it needs — a subset of Factory, Cron_Manager, Admin_Manager, and Ability_Manager.

Sybgo::init() orchestrates the startup sequence:

  1. All five modules have boot() called on them in order. Each boot() only registers on managers — it never executes domain logic directly (e.g. $this->cron->register(...), $this->admin->register_page(...)).
  2. Cron_Manager::init() is called — schedules all registered cron events and wires their add_action callbacks.
  3. Admin_Manager::init() is called (admin context only) — calls init() on each registered page and wires the cleanup handler and asset enqueuer.
  4. Ability_Manager::init() is deferred to the init WordPress action at priority 20 — modules register abilities at priority 5 on the same hook, so their registrations complete before the manager wires them into WP.

Callback methods on a module (e.g. freeze_report_callback(), cleanup_old_events_callback()) are public named methods, making them independently testable without running init().

Module Managers Responsibilities
Event_Module Ability_Manager Event Tracker init, sybgo_init_api(), sybgo/track-events ability
Report_Module Cron_Manager, Admin_Manager Dashboard_Widget, Reports_Page, freeze cron
Email_Module Cron_Manager Send and retry email crons
AI_Module Ability_Manager sybgo/generate-summary ability
Settings_Module Cron_Manager, Admin_Manager Settings_Page, cleanup cron, asset enqueuer, cleanup form handler

When adding a new domain feature, identify the appropriate existing module or create a new one. Do not add hooks or domain logic to class-sybgo.php.

WP7 Ability API Registrations

Sybgo registers two abilities with the WordPress 7 Ability API via Ability_Manager. Both are registered at init priority 5 so the sybgo text domain is loaded before __() evaluates, while Ability_Manager::init() (which wires them into WP) runs at priority 20.

Ability name Registered by Permission
sybgo/track-events Event_Module manage_options
sybgo/generate-summary AI_Module manage_options

sybgo/track-events confirms that the Event Tracker is active. sybgo/generate-summary proxies the AI summariser; its execute_callback returns null when the summariser is unavailable (WP < 7 or no AI transport configured).

Admin AJAX Actions

The dashboard widget registers two AJAX actions, both protected by the sybgo_widget_nonce nonce (key: nonce in the POST body).

Action Handler Capability Success response
sybgo_filter_events Dashboard_Widget::ajax_filter_events() read {html: string, count: int}
sybgo_widget_ai_summary Dashboard_Widget::ajax_widget_ai_summary() manage_options {summary: string}

The nonce value and ajaxUrl are available in the sybgoWidget JS object (localized by Dashboard_Widget::enqueue_assets()). See ajax-actions.md for full parameter and response documentation.

Admin Classes: Constructor Dependencies

Reports_Page accepts Aggregated_Event_Repository as its 7th constructor argument. This repository is used by render_php_errors_table() to query PHP error rows for a report's date range. When instantiating Reports_Page directly (e.g., in tests), pass an Aggregated_Event_Repository instance as the final argument.

Similarly, Dashboard_Widget accepts Aggregated_Event_Repository as its 6th constructor argument for the PHP Errors widget section.

Plugin Uninstall

When a user deletes the plugin from the WordPress admin, WordPress calls uninstall.php at the plugin root. This file bootstraps the autoloader and delegates all cleanup to Sybgo\Admin\Uninstaller::run().

Uninstaller performs three steps in order:

  1. Drop database tables — calls DatabaseManager::get_table_names() (static, no side effects) and issues DROP TABLE IF EXISTS for each table.
  2. Clear cron events — calls Cron_Manager::get_hooks() and passes each hook to wp_clear_scheduled_hook().
  3. Delete options — calls Settings_Page::get_option_names() and passes each name to delete_option().

Each of those static methods is the single source of truth for its identifiers, so adding a new table, hook, or option to the appropriate method is enough to ensure it is also cleaned up on uninstall.

The deactivation hook (Sybgo::deactivate()) only clears cron events. Full data removal (tables, options) happens only on uninstall, not on deactivation.

Adding New Event Types

1. Create Tracker Class

Create events/trackers/class-media-tracker.php:

namespace Rocket\Sybgo\Events\Trackers;

use Rocket\Sybgo\Database\Event_Repository;

class Media_Tracker {
    private Event_Repository $event_repo;

    public function __construct( Event_Repository $event_repo ) {
        $this->event_repo = $event_repo;
        add_filter( 'sybgo_event_types', array( $this, 'register_event_types' ) );
    }

    public function register_hooks(): void {
        add_action( 'add_attachment', array( $this, 'on_media_upload' ) );
    }

    public function register_event_types( array $types ): array {
        $types['media_uploaded'] = array(
            'icon'            => '📎',
            'stat_label'      => __( 'Media Uploads', 'sybgo' ),
            'short_title'     => function ( array $event_data ): string {
                return $event_data['object']['filename'];
            },
            'detailed_title'  => function ( array $event_data ): string {
                return 'Uploaded: ' . $event_data['object']['filename'];
            },
            'ai_description'  => function ( array $object, array $metadata ): string {
                return "File uploaded: {$object['filename']} ({$metadata['mime_type']})";
            },
            'describe'        => function ( array $event_data ): string {
                return "Event Type: Media Uploaded\nData: Filename, size, MIME type";
            },
        );
        return $types;
    }

    public function on_media_upload( int $attachment_id ): void {
        $event_data = [
            'action' => 'uploaded',
            'object' => [
                'type' => 'media',
                'id' => $attachment_id,
                'filename' => basename( get_attached_file( $attachment_id ) )
            ],
            'context' => [
                'user_id' => get_current_user_id(),
                'user_name' => wp_get_current_user()->display_name,
            ],
            'metadata' => [
                'file_size' => filesize( get_attached_file( $attachment_id ) ),
                'mime_type' => get_post_mime_type( $attachment_id )
            ]
        ];

        $this->event_repo->create( array(
            'event_type' => 'media_uploaded',
            'event_data' => $event_data,
        ) );
    }
}

2. Wire Up in Event_Tracker

Add to events/class-event-tracker.php in the load_trackers() method:

$this->trackers = array(
    'post'    => new Trackers\Post_Tracker( $this->event_repo ),
    'user'    => new Trackers\User_Tracker( $this->event_repo ),
    'update'  => new Trackers\Update_Tracker( $this->event_repo ),
    'comment' => new Trackers\Comment_Tracker( $this->event_repo ),
    'media'   => new Trackers\Media_Tracker( $this->event_repo ),
);

Event types registered via the sybgo_event_types filter are automatically picked up by the Report_Generator for totals and trends — no manual updates needed.

3. Write Tests

Create Tests/Unit/Events/MediaTrackerTest.php:

class MediaTrackerTest extends TestCase {
    public function test_track_media_upload() {
        // Test implementation
    }
}

Troubleshooting

Tests Failing

Check PHP version:

php -v  # Must be 7.4+

Update dependencies:

composer update

Clear cache:

composer dump-autoload

Code Standards Errors

Common issues:

  • Missing type declarations
  • Incorrect escaping functions
  • Wrong indentation (tabs vs spaces)
  • Missing DocBlocks

Fix automatically:

composer phpcs:fix

Cron Not Running

Check schedule:

wp cron event list

Test manually:

wp cron event run sybgo_freeze_weekly_report
wp cron event run sybgo_send_report_emails

Enable system cron:

# In crontab -e
*/15 * * * * wget -q -O - https://yoursite.local/wp-cron.php?doing_wp_cron

Performance Testing

Load Testing

# Generate test events
for i in {1..1000}; do
    wp post create --post_title="Test Post $i" --post_status=publish
done

# Check performance
time wp cron event run sybgo_freeze_weekly_report

Query Analysis

-- Explain slow queries
EXPLAIN SELECT * FROM wp_sybgo_events WHERE report_id IS NULL;

-- Check index usage
SHOW INDEXES FROM wp_sybgo_events;

Contributing

Before Submitting PR

  • All tests passing (composer run-tests)
  • Code standards passing (composer phpcs)
  • Documentation updated if needed
  • Commit messages are descriptive
  • No debug code left in

Commit Message Format

Add feature: Brief description

- Detailed point 1
- Detailed point 2

Closes #123

Related Documentation