No description
  • PHP 79.6%
  • JavaScript 15.5%
  • CSS 4.9%
Find a file
2026-01-21 10:44:15 -06:00
admin Enhance user creation reliability and add game mode selection UI 2026-01-08 22:40:50 -05:00
api/game-plugin-template Add comprehensive game plugin template system for framework 2026-01-09 12:17:27 -05:00
includes Refactor Game Sets player name handling and add debugging improvements 2026-01-21 10:36:35 -06:00
public Refactor Game Sets player name handling and add debugging improvements 2026-01-21 10:36:35 -06:00
templates/email Funnel broken; sends duplicate emails; bad redirect 2025-08-29 08:47:30 -05:00
.env.example Enhance debug logging system with dual JSON/human-readable output 2025-11-06 15:20:54 -06:00
.gitignore Excluded template backup 2026-01-16 05:53:29 -06:00
aiml-games-framework.php Begin fixing Game Sets 2026-01-16 15:51:49 -06:00
README.md Funnel working with custom emails 2025-09-08 22:35:29 -05:00
readme.txt Initial commit of Alpha version of AIML Games Framework WP Plugin 2025-07-30 22:23:18 -05:00
todo_list.md Fixed single template for Word Scramble games. 2025-08-18 20:36:09 -05:00
uninstall.php Add overall leaderboard system and Gutenberg blocks integration 2025-08-15 02:53:55 -05:00

AIML Games Framework

A comprehensive WordPress plugin framework for educational AI and machine learning games using a modern hub & spoke architecture with CPT-first content modeling.

Overview

The AIML Games Framework provides a centralized foundation for managing multiple educational game plugins. It implements a hub & spoke architecture where:

  • Hub (Framework): Provides shared services like user management, leaderboards, analytics, content types (CPTs), and admin interface
  • Spokes (Games): Individual game plugins that extend the framework for specific game types

This README documents the Custom Post Type (CPT) system architecture, developer workflows for aiml_word, aiml_game, and aiml_game_set, and taxonomy usage examples.

Key Features

Core Framework Services

  • User Management: Player profiles, progress tracking, achievements
  • Global Leaderboard: Cross-game scoring and rankings
  • Analytics: Gameplay metrics and reporting
  • CPT System: Word/game/game-set content modeling with meta and taxonomies
  • Game Registry: Plugin discovery and dependency management

Developer Tools

  • Abstract Base Class for games
  • Validation and logging
  • Admin partials and assets for CPT UI

CPT System Architecture

The framework registers three CPTs and two taxonomies, with standardized meta conventions and admin UI.

Post Types

  • aiml_word

    • Purpose: Canonical word entities with definition and extra data
    • UI: Visible in admin; non-public front-end by default; REST-enabled
    • Meta (canonical):
      • _aiml_word_definition (string)
      • _aiml_word_extra_json (JSON string; validated)
    • Taxonomies:
      • aiml_word_theme (hierarchical)
      • aiml_word_difficulty (hierarchical)
  • aiml_game

    • Purpose: A single playable game instance with settings
    • UI: Visible in admin; non-public front-end; REST-enabled
    • Meta:
      • _aiml_game_settings_json (JSON string; validated)
        • Game-specific settings live here (e.g., difficulty, round limits) by convention
  • aiml_game_set

    • Purpose: Ordered collection of aiml_game posts for progression/series
    • UI: Visible in admin; non-public front-end; REST-enabled
    • Meta:
      • _aiml_game_set_order (array of game post IDs; sanitized and deduped)

Registration and behaviors are implemented in includes/class-content-types.php.

Key reference identifiers from code:

  • CPT slugs:
    • AIML_Games_Content_Types::CPT_WORD
    • AIML_Games_Content_Types::CPT_GAME
    • AIML_Games_Content_Types::CPT_GAME_SET
  • Taxonomy slugs:
    • AIML_Games_Content_Types::TAX_WORD_THEME
    • AIML_Games_Content_Types::TAX_WORD_DIFFICULTY
  • Canonical meta keys:
    • AIML_Games_Content_Types::META_WORD_DEFINITION
    • AIML_Games_Content_Types::META_WORD_EXTRA_JSON
    • AIML_Games_Content_Types::META_GAME_SETTINGS_JSON
    • AIML_Games_Content_Types::META_GAME_SET_ORDER

Admin meta boxes are rendered via admin/partials/cpt-meta-boxes.php, with nonces and JSON validation. Admin assets are enqueued only on relevant screens.

Taxonomies

Both taxonomies attach to aiml_word and are hierarchical, UI-visible, REST-enabled:

  • aiml_word_theme
    • For thematic grouping (e.g., Animals, Countries)
  • aiml_word_difficulty
    • For difficulty grouping (e.g., Easy, Medium, Hard)

Default terms are ensured idempotently on each init by AIML_Games_Content_Types::ensure_default_terms().

Meta Naming Convention

Canonical meta keys follow: aiml{cpt}_{field}

  • cpt is the post type slug (aiml_word, aiml_game, aiml_game_set)
  • field is snake_case
  • JSON fields use a _json suffix

Admin UX and Safeguards

  • Meta boxes for each CPT with nonce verification and sanitization
  • JSON textareas validated; invalid JSON triggers an admin notice with actionable feedback
  • Game set membership saves ensure no cycles and sanitize to an ordered unique int list
  • Cache invalidation for affected helper buckets when posts change or delete

Developer Guide: Working with CPTs

This section covers standard workflows for aiml_word, aiml_game, and aiml_game_set, including CRUD, meta usage, taxonomy operations, and querying patterns.

aiml_word

Use aiml_word to store vocabulary used by games.

Create a word (secure example with sanitization and error handling):

// Raw input values (e.g., from a form or external source)
$raw_title       = 'algorithm';
$raw_content     = 'A step-by-step procedure for calculations.';
$raw_definition  = 'A step-by-step procedure for calculations.';
$raw_extra       = array( 'synonyms' => array( 'procedure', 'method' ) );

// Sanitize scalar text fields
$title      = sanitize_text_field( $raw_title );

// Allow only safe HTML in content (adjust allowed tags as needed)
$allowed_content_tags = array(
    'a'      => array( 'href' => array(), 'title' => array(), 'rel' => array(), 'target' => array() ),
    'em'     => array(),
    'strong' => array(),
    'p'      => array(),
    'br'     => array(),
    'ul'     => array(),
    'ol'     => array(),
    'li'     => array(),
);
$content    = wp_kses( $raw_content, $allowed_content_tags );

// Sanitize meta fields
$definition = sanitize_text_field( $raw_definition );

// Encode complex meta as JSON safely
$extra_json = wp_json_encode( $raw_extra );

// Insert the post
$post_id = wp_insert_post( array(
    'post_type'   => 'aiml_word',
    'post_status' => 'publish',
    'post_title'  => $title,
    'post_content'=> $content,
), true ); // Pass true to return WP_Error on failure

// Check for insertion errors
if ( is_wp_error( $post_id ) ) {
    // Handle the error appropriately (log it, display admin notice, etc.)
    error_log( sprintf( 'Failed to insert aiml_word: %s', $post_id->get_error_message() ) );
    return;
}

// Save sanitized meta only after successful insertion
update_post_meta( $post_id, '_aiml_word_definition', $definition );
update_post_meta( $post_id, '_aiml_word_extra_json', $extra_json );

Assign taxonomies:

wp_set_object_terms($post_id, array('Computer Science'), 'aiml_word_theme');
wp_set_object_terms($post_id, array('Medium'), 'aiml_word_difficulty');

Query words by taxonomy:

$words = get_posts(array(
  'post_type'      => 'aiml_word',
  'posts_per_page' => 50,
  'tax_query'      => array(
    'relation' => 'AND',
    array(
      'taxonomy' => 'aiml_word_theme',
      'field'    => 'name',
      'terms'    => array('Computer Science'),
    ),
    array(
      'taxonomy' => 'aiml_word_difficulty',
      'field'    => 'name',
      'terms'    => array('Medium'),
    ),
  ),
  'no_found_rows' => true,
));

Read meta safely with canonical-first pattern:

$def = get_post_meta($post_id, '_aiml_word_definition', true);
$extra_json = get_post_meta($post_id, '_aiml_word_extra_json', true);
$extra = is_string($extra_json) ? json_decode($extra_json, true) : array();

aiml_game

Stores a playable instance and its settings payload.

Create a game:

$game_id = wp_insert_post(array(
  'post_type'   => 'aiml_game',
  'post_status' => 'publish',
  'post_title'  => 'Word Scramble - Set 1 - Easy',
));

$settings = array(
  'game_type'           => 'word-scramble',
  'aiml_game_difficulty'=> 'easy',
  'aiml_game_max_rounds'=> 10,
  'ws_min_letters'      => 3,
  'ws_shuffle_seed'     => wp_generate_password(8, false),
);

update_post_meta($game_id, '_aiml_game_settings_json', wp_json_encode($settings));

Query games:

$games = get_posts(array(
  'post_type'      => 'aiml_game',
  'posts_per_page' => 20,
  'post_status'    => array('publish','draft','private'),
  'orderby'        => 'date',
  'order'          => 'DESC',
  'no_found_rows'  => true,
));

Load and validate settings:

$raw = get_post_meta($game_id, '_aiml_game_settings_json', true);
$settings = array();

if (is_string($raw) && $raw !== '') {
    $decoded = json_decode($raw, true);

    if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
        $settings = $decoded;
    } else {
        // Log the JSON error to help diagnose malformed data and avoid using invalid settings
        error_log(sprintf(
            '[aiml-games-framework] JSON decode error for game_id %d: %s; raw length=%d',
            (int) $game_id,
            function_exists('json_last_error_msg') ? json_last_error_msg() : ('code ' . json_last_error()),
            strlen($raw)
        ));
        $settings = array(); // reset to safe default
    }
}
 // Apply defensive defaults here as needed

aiml_game_set

Represents an ordered collection of aiml_game posts.

Create a set and assign game order:

$set_id = wp_insert_post(array(
  'post_type'   => 'aiml_game_set',
  'post_status' => 'publish',
  'post_title'  => 'Intro Series - Part 1',
));
$ordered_ids = array($game_id /*, ... more game IDs ... */);
update_post_meta($set_id, '_aiml_game_set_order', array_map('absint', $ordered_ids));

Fetch ordered games for a set:

$ordered = get_post_meta($set_id, '_aiml_game_set_order', true);
$ordered = is_array($ordered) ? array_values(array_unique(array_map('absint', $ordered))) : array();
if (!empty($ordered)) {
  $games = get_posts(array(
    'post_type'      => 'aiml_game',
    'post__in'       => $ordered,
    'orderby'        => 'post__in',
    'posts_per_page' => count($ordered),
    'no_found_rows'  => true,
  ));
}

Cycle protection:

  • The save handler prevents invalid or cyclical relationships per AIML_Games_Content_Types::save_game_set().

Admin Integration

Meta boxes:

  • aiml_game: Game Settings (JSON)
  • aiml_game_set: Games in Set (ordered list)
  • aiml_word: Word Details (definition, extra JSON)

Admin assets are conditionally enqueued only for these CPT screens; JSON and meta update errors surface as admin notices.

Shortcodes and Rendering

The framework provides a default game set shortcode to render progression and front-end UX:

Game plugins should use CPT-based queries for content.

Taxonomy Usage Examples

Create or ensure terms:

// Ensure a theme exists and get term ID, with robust WP_Error handling
$term = term_exists('Animals', 'aiml_word_theme');

// term_exists() may return 0, null, array, or WP_Error
if (is_wp_error($term)) {
  error_log(sprintf('[aiml-games-framework] term_exists error (theme): %s', $term->get_error_message()));
  return; // or handle gracefully in your context
}

if (!$term) {
  $term = wp_insert_term('Animals', 'aiml_word_theme');

  if (is_wp_error($term)) {
    error_log(sprintf('[aiml-games-framework] wp_insert_term error (theme): %s', $term->get_error_message()));
    return; // or handle gracefully in your context
  }
}

$theme_term_id = is_array($term) ? (int)$term['term_id'] : (int)$term;

// Ensure a difficulty term, explicitly checking for WP_Error
$diff = term_exists('Easy', 'aiml_word_difficulty');

if (is_wp_error($diff)) {
  error_log(sprintf('[aiml-games-framework] term_exists error (difficulty): %s', $diff->get_error_message()));
  return; // or handle gracefully
}

if (!$diff) {
  $diff = wp_insert_term('Easy', 'aiml_word_difficulty');

  if (is_wp_error($diff)) {
    error_log(sprintf('[aiml-games-framework] wp_insert_term error (difficulty): %s', $diff->get_error_message()));
    return; // or handle gracefully
  }
}

$diff_term_id = is_array($diff) ? (int)$diff['term_id'] : (int)$diff;

Assign terms to a word:

wp_set_object_terms($post_id, array($theme_term_id), 'aiml_word_theme', false);
wp_set_object_terms($post_id, array($diff_term_id), 'aiml_word_difficulty', false);

Query by theme and difficulty:

$words = get_posts(array(
  'post_type'      => 'aiml_word',
  'posts_per_page' => -1,
  'tax_query'      => array(
    'relation' => 'AND',
    array(
      'taxonomy' => 'aiml_word_theme',
      'field'    => 'term_id',
      'terms'    => array($theme_term_id),
    ),
    array(
      'taxonomy' => 'aiml_word_difficulty',
      'field'    => 'term_id',
      'terms'    => array($diff_term_id),
    ),
  ),
  'no_found_rows'  => true,
));

REST usage:

  • Both taxonomies are show_in_rest=true, so block editors and REST clients can manage terms programmatically.

Uninstall Behavior

The framework includes a comprehensive uninstall routine to clean up CPTs, taxonomies, tables, and options when appropriate dependency conditions are met.

  • See uninstall.php for complete vs partial cleanup rules:
    • If other aiml-games-* plugins are active, only framework-specific data is removed
    • If no related plugins remain, removes:
      • All aiml_game, aiml_game_set, aiml_word posts
      • aiml_word_theme and aiml_word_difficulty terms
      • Framework tables (leaderboard, analytics, sessions, user progress, etc.)
      • Options and user meta with aiml_games_ prefix
      • Flushes rewrite rules

Game plugins (e.g., Word Scramble) should preserve CPT data on their own uninstall; the framework owns shared CPT cleanup.

Debug Logging

The framework provides comprehensive debug logging through the aiml_debug_log() method. Debug logs use a mixed format combining human-readable and JSON entries:

Log Format Policy

  • Each log event generates two lines in the debug log file:
    1. Human-readable line: [timestamp UTC] [Plugin Name Debug] message | context=value | level=LEVEL
    2. JSON line: Complete structured data in NDJSON format for programmatic parsing

Mixed Format Example

[31-Dec-2024 23:45:12 UTC] [AIML Games Framework Debug] User creation started | context=user_creation | level=INFO
{"timestamp":"2024-12-31 23:45:12","timestamp_utc":"2024-12-31 23:45:12","level":"INFO","environment":"DEV","context":"user_creation","message":"User creation started","user_id":123,"ip_address":"127.0.0.1","request_id":"abc123","data":{"form_data":"..."}}

Consumer Guidelines

  • Human readers: Read human-readable lines for quick debugging
  • Log parsers: Skip non-JSON lines, parse JSON lines as NDJSON
  • Configuration:
    • Set AIML_DEBUG_LOG constant to enable logging
    • Use $hr_include_meta parameter to control human-readable metadata inclusion

Log File Location

  • Default: wp-content/aiml-debug.log
  • Configurable via AIML_DEBUG_LOG constant or method parameter
  • Creates parent directories automatically if needed

Troubleshooting

Common CPT issues:

  • Invalid JSON in meta fields:
    • Fix JSON and re-save; admin will show notices for errors
  • Missing default terms:
    • Defaults auto-heal on init via ensure_default_terms()
  • Permission issues:
    • CPTs use explicit capabilities for predictable access

Debug logging issues:

  • No debug output: Ensure AIML_DEBUG_LOG constant is defined or pass $enable_debug = true
  • Mixed format confusion: Parsers should skip non-JSON lines and parse JSON lines only
  • Directory permissions: Check write permissions on log directory

File References and Key Classes

Security and Performance Notes

  • Nonces, sanitization, and capability checks are enforced in CPT save handlers
  • JSON fields are validated with error reporting
  • Queries should use no_found_rows and field scoping to reduce overhead
  • Caches for helper buckets are invalidated on relevant post changes

Available Filters

aiml_games_word_scramble_fallback_url

Allows developers to provide a custom fallback URL for the Word Scramble game assets when the primary assets fail to load.

Parameters:

  • $url (mixed): Default value is false. Return a valid URL string to enable fallback asset loading.

Example usage:

add_filter('aiml_games_word_scramble_fallback_url', function($url) {
    return 'https://your-cdn.com/path/to/word-scramble-assets.js';
});

Use cases:

  • Provide CDN fallback URLs
  • Use local backup asset files
  • Implement custom asset delivery logic

Changelog (CPT docs)

  • Updated to include CPT architecture, migration flow, developer usage for aiml_word, aiml_game, aiml_game_set, and taxonomy examples aligned with implementation plan and current code.