reference plugin and image gen analysis

This commit is contained in:
Desktop
2025-11-11 21:16:37 +05:00
parent fedf415646
commit f4d62448cf
111 changed files with 49595 additions and 3988 deletions

View File

@@ -1,247 +0,0 @@
# IGNY8 AI System Audit — Execution Plan
## Objective
Perform a complete structural and functional audit of the IGNY8 AI subsystem exactly as it exists, without any modifications, renaming, or assumptions. Document all findings in a baseline report.
## Scope
### Primary Directory: `backend/igny8_core/ai/`
**Core AI Files (15 files):**
- `__init__.py` - Package initialization and exports
- `admin.py` - Django admin configuration for AI models
- `ai_core.py` - Core AI functionality
- `apps.py` - Django app configuration
- `base.py` - Base classes or utilities
- `constants.py` - AI-related constants
- `engine.py` - AI engine implementation
- `models.py` - Database models for AI entities
- `processor.py` - AI processing logic
- `prompts.py` - Prompt templates and management
- `registry.py` - Function/component registry
- `settings.py` - AI-specific settings
- `tasks.py` - Celery task definitions
- `tracker.py` - Progress tracking and state management
- `types.py` - Type definitions and schemas
- `validators.py` - Validation logic
**AI Functions Subdirectory (5 files):**
- `functions/__init__.py` - Function package exports
- `functions/auto_cluster.py` - Automatic clustering functionality
- `functions/generate_content.py` - Content generation logic
- `functions/generate_ideas.py` - Idea generation logic
- `functions/generate_images.py` - Image generation logic
### Related Directories
**`backend/igny8_core/utils/` (4 files):**
- `ai_processor.py` - AI processing utilities
- `content_normalizer.py` - Content normalization utilities
- `queue_manager.py` - Queue management utilities
- `wordpress.py` - WordPress integration utilities
**`backend/igny8_core/modules/` (AI-related files):**
- `planner/tasks.py` - Planner module Celery tasks
- `writer/tasks.py` - Writer module Celery tasks
- `system/models.py` - System models (may contain AI settings)
- `system/settings_models.py` - Settings models
- `system/settings_views.py` - Settings views
- `system/views.py` - System views
- `system/utils.py` - System utilities
**Configuration Files:**
- `backend/igny8_core/celery.py` - Celery configuration and task registration
- `backend/igny8_core/settings.py` - Django settings (AI configuration loading)
## Audit Methodology
### Phase 1: File Inventory and Initial Reading
1. Read all files in `backend/igny8_core/ai/` directory
2. Read all files in `backend/igny8_core/ai/functions/` directory
3. Read AI-related files in `backend/igny8_core/utils/`
4. Read AI-related task files in `backend/igny8_core/modules/`
5. Read configuration and integration files
### Phase 2: Function and Class Analysis
1. Extract all function definitions with:
- Function name
- Parameters and types
- Return values
- Docstrings/documentation
- Decorators (especially Celery tasks)
2. Extract all class definitions with:
- Class name
- Inheritance hierarchy
- Methods and their purposes
- Class-level attributes
3. Identify call sites for each function/class method
### Phase 3: Dependency Mapping
1. Map import relationships:
- Which files import from which files
- External dependencies (libraries, Django, Celery)
- Circular dependencies (if any)
2. Create dependency graph/table showing:
- Direct imports
- Indirect dependencies
- Shared utilities
### Phase 4: System Flow Analysis
1. Trace request flow:
- Frontend API endpoints → Views/Serializers
- Views → Celery tasks
- Celery tasks → AI functions
- AI functions → External APIs/Models
- Results → Database storage
- Results → Response to frontend
2. Document:
- Entry points (API endpoints, admin actions, management commands)
- Task queue flow (Celery task registration and execution)
- State management (tracker, progress updates)
- Error handling paths
- Logging and debug output
### Phase 5: Integration Points Analysis
1. **Celery Integration:**
- Task registration in `celery.py`
- Task decorators and configurations
- Task routing and queues
- Async execution patterns
2. **Database Integration:**
- Models used by AI subsystem
- Model relationships
- Data persistence patterns
- Query patterns
3. **Frontend Integration:**
- API endpoints that trigger AI tasks
- Serializers for AI data
- Response formats
- WebSocket/SSE for progress updates (if any)
4. **Configuration Integration:**
- Settings loading (Django settings, environment variables)
- Model/provider configuration
- API key management
- Feature flags or switches
5. **Debug Panel Integration:**
- Debug logging mechanisms
- Progress tracking
- State inspection tools
### Phase 6: Redundancy and Pattern Identification
1. Identify:
- Duplicated code blocks
- Similar functions with slight variations
- Repeated patterns that could indicate consolidation opportunities
- Unused or dead code
- Overlapping responsibilities
2. Document patterns:
- Common error handling approaches
- Repeated validation logic
- Similar processing pipelines
- Shared utility patterns
### Phase 7: Documentation Compilation
Create structured document with sections:
1. **Current File Inventory** - List all files with brief role descriptions
2. **Function Inventory** - Comprehensive list of all functions with descriptions
3. **Class Inventory** - All classes and their purposes
4. **Dependency Graph/Table** - Import relationships and dependencies
5. **System Flow Description** - End-to-end flow documentation
6. **Integration Points** - Detailed integration documentation
7. **Identified Redundancies** - Patterns and duplications found
8. **Summary of Potential Consolidation Areas** - Observations only (no refactoring proposals)
## Execution Rules
### Strict Guidelines:
-**DO:** Read all code exactly as written
-**DO:** Document what exists without modification
-**DO:** Label any assumptions explicitly
-**DO:** Trace actual code paths, not theoretical ones
-**DO:** Include line numbers and file paths for references
### Prohibited Actions:
-**DON'T:** Rename anything
-**DON'T:** Merge or consolidate code
-**DON'T:** Propose new architecture
-**DON'T:** Suggest simplifications
-**DON'T:** Make any code changes
-**DON'T:** Create new files (except the audit document)
-**DON'T:** Assume functionality without reading code
## Deliverable
**Document Title:** `IGNY8_AI_SYSTEM_AUDIT_BASELINE_REPORT.md`
**Structure:**
```markdown
# IGNY8 AI System Audit — Current Structure & Flow Mapping (Baseline Report)
## Executive Summary
[Brief overview of findings]
## 1. Current File Inventory
[Complete list with descriptions]
## 2. Function Inventory
[All functions documented]
## 3. Class Inventory
[All classes documented]
## 4. Dependency Graph/Table
[Import relationships]
## 5. System Flow Description
[End-to-end flows]
## 6. Integration Points
[Celery, Database, Frontend, Configuration, Debug]
## 7. Identified Redundancies or Repetition
[Patterns found]
## 8. Summary of Potential Consolidation Areas
[Observations only]
## 9. Assumptions Made
[Any assumptions explicitly labeled]
## 10. Appendix
[Additional details, code snippets, etc.]
```
## Execution Checklist
- [ ] Phase 1: Read all AI core files
- [ ] Phase 1: Read all AI function files
- [ ] Phase 1: Read all utility files
- [ ] Phase 1: Read all module task files
- [ ] Phase 1: Read configuration files
- [ ] Phase 2: Extract and document all functions
- [ ] Phase 2: Extract and document all classes
- [ ] Phase 3: Map all import dependencies
- [ ] Phase 4: Trace system flows
- [ ] Phase 5: Document integration points
- [ ] Phase 6: Identify redundancies
- [ ] Phase 7: Compile final audit document
## Estimated File Count
- **AI Core Files:** 15 files
- **AI Functions:** 5 files
- **Utilities:** 4 files
- **Module Tasks:** 2 files
- **System Module:** ~5 files
- **Configuration:** 2 files
- **Total:** ~33 files to analyze
## Notes
- This is a discovery phase only
- All findings must be based on actual code
- No refactoring or improvements will be proposed
- The goal is to understand the current state completely

View File

@@ -1,771 +0,0 @@
# IGNY8 AI System Audit — Current Structure & Flow Mapping (Baseline Report)
**Date:** 2024-12-19
**Scope:** Complete structural and functional audit of the IGNY8 AI subsystem
**Methodology:** Code analysis without modifications or assumptions
---
## Executive Summary
The IGNY8 AI subsystem is a comprehensive framework for AI-powered content operations including keyword clustering, idea generation, content generation, and image generation. The system uses a unified framework architecture with a centralized execution engine, but also maintains legacy code paths for backward compatibility.
**Key Findings:**
- **20 core AI files** in `backend/igny8_core/ai/`
- **4 AI function implementations** following BaseAIFunction pattern
- **Dual execution paths:** New unified framework (`run_ai_task``AIEngine`) and legacy paths (direct task calls)
- **Centralized AI request handling** via `AICore.run_ai_request()`
- **Comprehensive tracking** via `StepTracker`, `ProgressTracker`, `CostTracker`, and `ConsoleStepTracker`
- **Multiple prompt management systems:** `PromptRegistry` (new) and direct database queries (legacy)
---
## 1. Current File Inventory
### 1.1 Core AI Framework (`backend/igny8_core/ai/`)
| File | Lines | Purpose |
|------|-------|---------|
| `__init__.py` | 73 | Package exports - exposes main classes and functions |
| `admin.py` | 60 | Django admin configuration for `AITaskLog` model |
| `ai_core.py` | 756 | Centralized AI request handler - `AICore` class with `run_ai_request()` and `generate_image()` |
| `apps.py` | 21 | Django app configuration |
| `base.py` | 95 | Abstract base class `BaseAIFunction` - defines function interface |
| `constants.py` | 42 | Model pricing, valid models, configuration constants |
| `engine.py` | 375 | Central orchestrator `AIEngine` - manages function lifecycle |
| `models.py` | 52 | Database model `AITaskLog` for unified logging |
| `processor.py` | 76 | **DEPRECATED** wrapper around `AICore` for backward compatibility |
| `prompts.py` | 432 | `PromptRegistry` - centralized prompt management with hierarchical resolution |
| `registry.py` | 97 | Function registry with lazy loading - `register_function()`, `get_function()` |
| `settings.py` | 117 | Model configurations per function - `MODEL_CONFIG`, `get_model_config()` |
| `tasks.py` | 131 | Unified Celery task entrypoint `run_ai_task()` - single entry point for all AI functions |
| `tracker.py` | 348 | Progress tracking utilities - `StepTracker`, `ProgressTracker`, `CostTracker`, `ConsoleStepTracker` |
| `types.py` | 44 | Type definitions - `StepLog`, `ProgressState`, `AITaskResult` dataclasses |
| `validators.py` | 187 | Validation functions - `validate_ids()`, `validate_keywords_exist()`, etc. |
### 1.2 AI Function Implementations (`backend/igny8_core/ai/functions/`)
| File | Lines | Purpose |
|------|-------|---------|
| `__init__.py` | 18 | Function package exports |
| `auto_cluster.py` | 330 | `AutoClusterFunction` - Groups keywords into semantic clusters |
| `generate_content.py` | 388 | `GenerateContentFunction` + `generate_content_core()` - Generates article content |
| `generate_ideas.py` | 335 | `GenerateIdeasFunction` + `generate_ideas_core()` - Generates content ideas from clusters |
| `generate_images.py` | 279 | `GenerateImagesFunction` + `generate_images_core()` - Generates images for tasks |
### 1.3 Utility Files (`backend/igny8_core/utils/`)
| File | Lines | Purpose |
|------|-------|---------|
| `ai_processor.py` | 1407 | **LEGACY** Unified AI interface - `AIProcessor` class with OpenAI/Runware support. Contains duplicate constants and methods. |
| `content_normalizer.py` | 273 | Content normalization - converts AI responses to HTML format |
| `queue_manager.py` | 90 | Queue abstraction (currently placeholder, not fully implemented) |
| `wordpress.py` | (not read) | WordPress integration utilities |
### 1.4 Module Task Files
| File | Lines | Purpose |
|------|-------|---------|
| `modules/planner/tasks.py` | 736 | **DEPRECATED** Legacy clustering task `auto_cluster_keywords_task()` - uses old `AIProcessor` |
| `modules/writer/tasks.py` | 1156 | Legacy content/image generation tasks - `auto_generate_content_task()`, `auto_generate_images_task()` |
### 1.5 Configuration Files
| File | Purpose |
|------|---------|
| `celery.py` | Celery app configuration - auto-discovers tasks from all Django apps |
| `modules/system/models.py` | `AIPrompt`, `IntegrationSettings` models for AI configuration |
---
## 2. Function Inventory
### 2.1 Core Framework Functions
#### `AICore` (ai_core.py)
- **`__init__(account=None)`** - Initialize with account context, loads API keys and model from `IntegrationSettings`
- **`_load_account_settings()`** - Loads OpenAI/Runware API keys and model from `IntegrationSettings` or Django settings
- **`get_api_key(integration_type='openai')`** - Returns API key for integration type
- **`get_model(integration_type='openai')`** - Returns model name for integration type
- **`run_ai_request(prompt, model=None, max_tokens=4000, temperature=0.7, response_format=None, api_key=None, function_name='ai_request', function_id=None, tracker=None)`** - **CENTRAL METHOD** - Handles all OpenAI text generation requests with console logging
- **`extract_json(response_text)`** - Extracts JSON from response text (handles markdown code blocks)
- **`generate_image(prompt, provider='openai', model=None, size='1024x1024', n=1, api_key=None, negative_prompt=None, function_name='generate_image')`** - Generates images via OpenAI DALL-E or Runware
- **`_generate_image_openai(...)`** - Internal method for OpenAI image generation
- **`_generate_image_runware(...)`** - Internal method for Runware image generation
- **`calculate_cost(model, input_tokens, output_tokens, model_type='text')`** - Calculates API cost
- **`call_openai(...)`** - **LEGACY** - Redirects to `run_ai_request()`
#### `AIEngine` (engine.py)
- **`__init__(celery_task=None, account=None)`** - Initialize with Celery task and account
- **`execute(fn: BaseAIFunction, payload: dict)`** - **CENTRAL ORCHESTRATOR** - Unified execution pipeline:
- Phase 1: INIT (0-10%) - Validation
- Phase 2: PREP (10-25%) - Data loading & prompt building
- Phase 3: AI_CALL (25-70%) - API call to provider
- Phase 4: PARSE (70-85%) - Response parsing
- Phase 5: SAVE (85-98%) - Database operations
- Phase 6: DONE (98-100%) - Finalization
- **`_handle_error(error, fn=None, exc_info=False)`** - Centralized error handling
- **`_log_to_database(fn, payload, parsed, save_result, error=None)`** - Logs to `AITaskLog` model
- **`_calculate_credits_for_clustering(keyword_count, tokens, cost)`** - Calculates credits for clustering operations
#### `PromptRegistry` (prompts.py)
- **`get_prompt(function_name, account=None, task=None, context=None)`** - Hierarchical prompt resolution:
1. Task-level `prompt_override` (if exists)
2. DB prompt for (account, function)
3. Default fallback from registry
- **`_render_prompt(prompt_template, context)`** - Renders template with `[IGNY8_*]` placeholders and `{variable}` format
- **`get_image_prompt_template(account=None)`** - Gets image prompt template
- **`get_negative_prompt(account=None)`** - Gets negative prompt
#### `BaseAIFunction` (base.py) - Abstract Interface
- **`get_name()`** - Returns function name (abstract)
- **`get_metadata()`** - Returns function metadata (display name, description, phases)
- **`validate(payload, account=None)`** - Validates input payload (default: checks for 'ids')
- **`get_max_items()`** - Returns max items limit (optional)
- **`prepare(payload, account=None)`** - Loads and prepares data (abstract)
- **`build_prompt(data, account=None)`** - Builds AI prompt (abstract)
- **`get_model(account=None)`** - Returns model override (optional)
- **`parse_response(response, step_tracker=None)`** - Parses AI response (abstract)
- **`save_output(parsed, original_data, account=None, progress_tracker=None, step_tracker=None)`** - Saves results to database (abstract)
### 2.2 AI Function Implementations
#### `AutoClusterFunction` (functions/auto_cluster.py)
- **`get_name()`** - Returns `'auto_cluster'`
- **`validate(payload, account=None)`** - Validates keyword IDs exist
- **`prepare(payload, account=None)`** - Loads keywords with relationships
- **`build_prompt(data, account=None)`** - Builds clustering prompt using `PromptRegistry`
- **`parse_response(response, step_tracker=None)`** - Parses JSON cluster data
- **`save_output(parsed, original_data, account=None, ...)`** - Creates/updates clusters and assigns keywords
#### `GenerateIdeasFunction` (functions/generate_ideas.py)
- **`get_name()`** - Returns `'generate_ideas'`
- **`validate(payload, account=None)`** - Validates cluster IDs exist
- **`prepare(payload, account=None)`** - Loads clusters with keywords
- **`build_prompt(data, account=None)`** - Builds ideas generation prompt
- **`parse_response(response, step_tracker=None)`** - Parses JSON ideas data
- **`save_output(parsed, original_data, account=None, ...)`** - Creates `ContentIdeas` records
#### `GenerateContentFunction` (functions/generate_content.py)
- **`get_name()`** - Returns `'generate_content'`
- **`validate(payload, account=None)`** - Validates task IDs exist
- **`prepare(payload, account=None)`** - Loads tasks with relationships
- **`build_prompt(data, account=None)`** - Builds content generation prompt
- **`parse_response(response, step_tracker=None)`** - Parses JSON or plain text content
- **`save_output(parsed, original_data, account=None, ...)`** - Saves content to `Content` model
#### `GenerateImagesFunction` (functions/generate_images.py)
- **`get_name()`** - Returns `'generate_images'`
- **`validate(payload, account=None)`** - Validates task IDs exist
- **`prepare(payload, account=None)`** - Loads tasks and image settings
- **`build_prompt(data, account=None)`** - Extracts image prompts from task content (calls AI)
- **`parse_response(response, step_tracker=None)`** - Returns parsed response (already parsed)
- **`save_output(parsed, original_data, account=None, ...)`** - Creates `Images` records
### 2.3 Tracking Functions
#### `StepTracker` (tracker.py)
- **`add_request_step(step_name, status='success', message='', error=None, duration=None)`** - Adds request step
- **`add_response_step(step_name, status='success', message='', error=None, duration=None)`** - Adds response step
- **`get_meta()`** - Returns metadata dict with request/response steps
#### `ProgressTracker` (tracker.py)
- **`update(phase, percentage, message, current=None, total=None, current_item=None, meta=None)`** - Updates Celery task state
- **`set_phase(phase, percentage, message, meta=None)`** - Sets progress phase
- **`complete(message='Task complete!', meta=None)`** - Marks task as complete
- **`error(error_message, meta=None)`** - Marks task as failed
- **`get_duration()`** - Returns elapsed time in milliseconds
#### `ConsoleStepTracker` (tracker.py)
- **`init(message='Task started')`** - Logs initialization
- **`prep(message)`** - Logs preparation phase
- **`ai_call(message)`** - Logs AI call phase
- **`parse(message)`** - Logs parsing phase
- **`save(message)`** - Logs save phase
- **`done(message='Execution completed')`** - Logs completion
- **`error(error_type, message, exception=None)`** - Logs error
- **`retry(attempt, max_attempts, reason='')`** - Logs retry
- **`timeout(timeout_seconds)`** - Logs timeout
- **`rate_limit(retry_after)`** - Logs rate limit
- **`malformed_json(details='')`** - Logs JSON parsing error
#### `CostTracker` (tracker.py)
- **`record(function_name, cost, tokens, model=None)`** - Records API call cost
- **`get_total()`** - Returns total cost
- **`get_total_tokens()`** - Returns total tokens
- **`get_operations()`** - Returns all operations list
### 2.4 Celery Tasks
#### `run_ai_task` (ai/tasks.py)
- **`run_ai_task(self, function_name: str, payload: dict, account_id: int = None)`** - **UNIFIED ENTRYPOINT** - Dynamically loads and executes AI functions via `AIEngine`
#### Legacy Tasks (modules/*/tasks.py)
- **`auto_cluster_keywords_task`** (planner/tasks.py) - **DEPRECATED** - Uses old `AIProcessor`
- **`auto_generate_content_task`** (writer/tasks.py) - Uses `AIProcessor` directly (not via framework)
- **`auto_generate_images_task`** (writer/tasks.py) - Uses `AIProcessor` directly
### 2.5 Legacy Functions (ai_processor.py)
#### `AIProcessor` - **LEGACY/DEPRECATED**
- **`_call_openai(prompt, model=None, max_tokens=4000, temperature=0.7, response_format=None, api_key=None, function_id=None, response_steps=None)`** - Internal OpenAI API caller
- **`_extract_json_from_response(response_text)`** - JSON extraction (duplicate of `AICore.extract_json()`)
- **`generate_content(prompt, model=None, max_tokens=4000, temperature=0.7, **kwargs)`** - Generates text content
- **`extract_image_prompts(content, title, max_images=3, account=None)`** - Extracts image prompts from content
- **`check_moderation(text, api_key=None)`** - Checks content moderation
- **`generate_image(prompt, provider='openai', model=None, size='1024x1024', n=1, api_key=None, **kwargs)`** - Generates images
- **`cluster_keywords(keywords, sector_name=None, account=None, response_steps=None, progress_callback=None, tracker=None, **kwargs)`** - **DEPRECATED** - Clusters keywords (old method)
- **`generate_ideas(clusters, account=None, **kwargs)`** - Generates ideas (old method)
- **`get_prompt(prompt_type, account=None)`** - Gets prompt from database (old method)
- **`estimate_cost(operation, tokens_or_prompt, model=None)`** - Estimates cost (not implemented)
---
## 3. Class Inventory
### 3.1 Core Classes
| Class | File | Purpose | Inheritance |
|-------|------|---------|-------------|
| `AICore` | ai_core.py | Centralized AI request handler | - |
| `AIEngine` | engine.py | Central orchestrator for AI functions | - |
| `BaseAIFunction` | base.py | Abstract base for all AI functions | ABC |
| `PromptRegistry` | prompts.py | Centralized prompt management | - |
| `StepTracker` | tracker.py | Tracks request/response steps | - |
| `ProgressTracker` | tracker.py | Tracks Celery progress updates | - |
| `CostTracker` | tracker.py | Tracks API costs and tokens | - |
| `ConsoleStepTracker` | tracker.py | Console-based step logging | - |
| `AITaskLog` | models.py | Database model for AI task logging | AccountBaseModel |
| `AIProcessor` | utils/ai_processor.py | **LEGACY** Unified AI interface | - |
### 3.2 Function Classes
| Class | File | Purpose | Inheritance |
|-------|------|---------|-------------|
| `AutoClusterFunction` | functions/auto_cluster.py | Keyword clustering | BaseAIFunction |
| `GenerateIdeasFunction` | functions/generate_ideas.py | Idea generation | BaseAIFunction |
| `GenerateContentFunction` | functions/generate_content.py | Content generation | BaseAIFunction |
| `GenerateImagesFunction` | functions/generate_images.py | Image generation | BaseAIFunction |
### 3.3 Data Classes
| Class | File | Purpose |
|-------|------|---------|
| `StepLog` | types.py | Single step in request/response tracking |
| `ProgressState` | types.py | Progress state for AI tasks |
| `AITaskResult` | types.py | Result from AI function execution |
---
## 4. Dependency Graph/Table
### 4.1 Import Relationships
```
ai/__init__.py
├─> registry.py (register_function, get_function, list_functions)
├─> engine.py (AIEngine)
├─> base.py (BaseAIFunction)
├─> ai_core.py (AICore)
├─> validators.py (all validators)
├─> constants.py (all constants)
├─> prompts.py (PromptRegistry, get_prompt)
└─> settings.py (MODEL_CONFIG, get_model_config, etc.)
ai/tasks.py
├─> engine.py (AIEngine)
└─> registry.py (get_function_instance)
ai/engine.py
├─> base.py (BaseAIFunction)
├─> tracker.py (StepTracker, ProgressTracker, CostTracker, ConsoleStepTracker)
├─> ai_core.py (AICore)
└─> settings.py (get_model_config)
ai/ai_core.py
├─> constants.py (MODEL_RATES, IMAGE_MODEL_RATES, etc.)
└─> tracker.py (ConsoleStepTracker)
ai/functions/auto_cluster.py
├─> base.py (BaseAIFunction)
├─> ai_core.py (AICore)
├─> prompts.py (PromptRegistry)
└─> settings.py (get_model_config)
ai/functions/generate_content.py
├─> base.py (BaseAIFunction)
├─> ai_core.py (AICore)
├─> prompts.py (PromptRegistry)
└─> settings.py (get_model_config)
ai/functions/generate_ideas.py
├─> base.py (BaseAIFunction)
├─> ai_core.py (AICore)
├─> prompts.py (PromptRegistry)
└─> settings.py (get_model_config)
ai/functions/generate_images.py
├─> base.py (BaseAIFunction)
├─> ai_core.py (AICore)
├─> prompts.py (PromptRegistry)
└─> settings.py (get_model_config)
utils/ai_processor.py
├─> modules/system/models.py (IntegrationSettings)
└─> modules/system/utils.py (get_prompt_value, get_default_prompt)
modules/planner/tasks.py
└─> utils/ai_processor.py (AIProcessor) [DEPRECATED PATH]
modules/writer/tasks.py
├─> utils/ai_processor.py (AIProcessor) [LEGACY PATH]
└─> ai/functions/generate_content.py (generate_content_core)
```
### 4.2 External Dependencies
| Dependency | Used By | Purpose |
|------------|---------|---------|
| `django` | All files | Django ORM, models, settings |
| `celery` | tasks.py, engine.py | Async task execution |
| `requests` | ai_core.py, ai_processor.py | HTTP requests to OpenAI/Runware APIs |
| `json` | Multiple files | JSON parsing |
| `re` | ai_core.py, ai_processor.py, content_normalizer.py | Regex for JSON extraction |
| `logging` | All files | Logging |
| `time` | tracker.py, ai_core.py | Timing and duration tracking |
| `bs4` (BeautifulSoup) | content_normalizer.py | HTML parsing (optional) |
### 4.3 Database Models Dependencies
| Model | Used By | Purpose |
|-------|---------|---------|
| `AITaskLog` | engine.py | Unified AI task logging |
| `IntegrationSettings` | ai_core.py, ai_processor.py, settings.py | API keys and model configuration |
| `AIPrompt` | prompts.py | Custom prompt templates |
| `Keywords` | auto_cluster.py, validators.py | Keyword data |
| `Clusters` | auto_cluster.py, generate_ideas.py | Cluster data |
| `ContentIdeas` | generate_ideas.py | Content ideas |
| `Tasks` | generate_content.py, generate_images.py | Writer tasks |
| `Content` | generate_content.py | Generated content |
| `Images` | generate_images.py | Generated images |
---
## 5. System Flow Description
### 5.1 New Unified Framework Flow (Recommended Path)
```
Frontend API Call
ViewSet Action (e.g., planner/views.py::auto_cluster)
run_ai_task.delay(function_name='auto_cluster', payload={ids: [...]}, account_id=123)
Celery Worker: run_ai_task (ai/tasks.py)
├─> Load Account
├─> get_function_instance('auto_cluster') → AutoClusterFunction
└─> AIEngine.execute(AutoClusterFunction, payload)
├─> Phase 1: INIT (0-10%)
│ └─> fn.validate(payload, account)
├─> Phase 2: PREP (10-25%)
│ ├─> fn.prepare(payload, account) → Load keywords
│ └─> fn.build_prompt(data, account) → PromptRegistry.get_prompt()
├─> Phase 3: AI_CALL (25-70%)
│ ├─> AICore.run_ai_request(prompt, model, ...)
│ │ ├─> Load API key from IntegrationSettings
│ │ ├─> Validate model
│ │ ├─> Build OpenAI request
│ │ ├─> Send HTTP request
│ │ ├─> Parse response
│ │ └─> Calculate cost
│ └─> Track cost via CostTracker
├─> Phase 4: PARSE (70-85%)
│ └─> fn.parse_response(response_content, step_tracker)
├─> Phase 5: SAVE (85-98%)
│ └─> fn.save_output(parsed, original_data, account, ...)
│ └─> Database transaction: Create/update clusters
└─> Phase 6: DONE (98-100%)
├─> Log to AITaskLog
└─> Return result dict
Celery Task State Update (SUCCESS/FAILURE)
Frontend Polls Task Status
Progress Modal Displays Steps
```
### 5.2 Legacy Content Generation Flow (Still Active)
```
Frontend API Call
ViewSet Action (writer/views.py::auto_generate_content)
auto_generate_content_task.delay(task_ids, account_id)
Celery Worker: auto_generate_content_task (modules/writer/tasks.py)
├─> Load Tasks from database
├─> For each task:
│ ├─> Load prompt template (get_prompt_value)
│ ├─> Format prompt with task data
│ ├─> AIProcessor.generate_content(prompt)
│ │ └─> AIProcessor._call_openai() [DUPLICATE OF AICore.run_ai_request]
│ ├─> Parse response (GenerateContentFunction.parse_response)
│ └─> Save content (GenerateContentFunction.save_output)
└─> Return result
```
### 5.3 Legacy Clustering Flow (Deprecated)
```
Frontend API Call
ViewSet Action (planner/views.py::auto_cluster) [OLD PATH]
_auto_cluster_keywords_core() (modules/planner/tasks.py)
├─> Load keywords
├─> AIProcessor.cluster_keywords() [DEPRECATED]
│ └─> AIProcessor._call_openai() [DUPLICATE]
├─> Parse clusters
└─> Save to database
```
### 5.4 Image Generation Flow
```
Frontend API Call
ViewSet Action (writer/views.py::auto_generate_images)
auto_generate_images_task.delay(task_ids, account_id)
Celery Worker: auto_generate_images_task (modules/writer/tasks.py)
├─> Load tasks
├─> For each task:
│ ├─> Extract image prompts (AIProcessor.extract_image_prompts)
│ │ └─> Calls AI to extract prompts from content
│ ├─> Generate featured image (AIProcessor.generate_image)
│ ├─> Generate desktop images (if enabled)
│ └─> Generate mobile images (if enabled)
└─> Save Images records
```
---
## 6. Integration Points
### 6.1 Celery Integration
**Task Registration:**
- `celery.py` uses `app.autodiscover_tasks()` to auto-discover tasks from all Django apps
- Tasks are registered via `@shared_task` decorator
**Registered Tasks:**
1. `ai.tasks.run_ai_task` - Unified entrypoint (NEW)
2. `planner.tasks.auto_cluster_keywords_task` - **DEPRECATED**
3. `writer.tasks.auto_generate_content_task` - Legacy (still active)
4. `writer.tasks.auto_generate_images_task` - Legacy (still active)
**Task State Management:**
- `ProgressTracker.update()` calls `task.update_state(state='PROGRESS', meta={...})`
- Progress metadata includes: `phase`, `percentage`, `message`, `request_steps`, `response_steps`
- Final states: `SUCCESS`, `FAILURE`
### 6.2 Database Integration
**Models:**
- `AITaskLog` - Unified logging table (used by `AIEngine._log_to_database()`)
- `IntegrationSettings` - API keys and model configuration (used by `AICore._load_account_settings()`)
- `AIPrompt` - Custom prompt templates (used by `PromptRegistry.get_prompt()`)
**Data Models:**
- `Keywords` - Input for clustering
- `Clusters` - Output of clustering
- `ContentIdeas` - Output of idea generation
- `Tasks` - Input for content/image generation
- `Content` - Output of content generation
- `Images` - Output of image generation
### 6.3 Frontend API Integration
**Endpoints:**
1. `POST /v1/planner/keywords/auto_cluster/``planner.views.ClusterViewSet.auto_cluster()`
- Calls `run_ai_task.delay(function_name='auto_cluster', ...)`
2. `POST /v1/planner/clusters/auto_generate_ideas/``planner.views.ClusterViewSet.auto_generate_ideas()`
- Calls `run_ai_task.delay(function_name='auto_generate_ideas', ...)`
3. `POST /v1/writer/tasks/auto_generate_content/``writer.views.TasksViewSet.auto_generate_content()`
- Calls `auto_generate_content_task.delay(...)` [LEGACY PATH]
4. `POST /v1/writer/tasks/auto_generate_images/``writer.views.TasksViewSet.auto_generate_images()`
- Calls `auto_generate_images_task.delay(...)` [LEGACY PATH]
**Response Format:**
- Success: `{success: true, task_id: "...", message: "..."}`
- Error: `{success: false, error: "..."}`
**Progress Tracking:**
- Frontend polls Celery task status via `GET /v1/system/tasks/{task_id}/status/`
- Progress modal displays `request_steps` and `response_steps` from task meta
### 6.4 Configuration Integration
**Settings Loading Hierarchy:**
1. **Account-level** (`IntegrationSettings` model):
- API keys: `IntegrationSettings.config['apiKey']`
- Model: `IntegrationSettings.config['model']`
- Image settings: `IntegrationSettings.config` (for image_generation type)
2. **Django Settings** (fallback):
- `OPENAI_API_KEY`
- `RUNWARE_API_KEY`
- `DEFAULT_AI_MODEL`
3. **Function-level** (`ai/settings.py`):
- `MODEL_CONFIG[function_name]` - Default model, max_tokens, temperature per function
**Prompt Loading Hierarchy:**
1. Task-level override: `task.prompt_override` (if exists)
2. Account-level: `AIPrompt` model (account, prompt_type)
3. Default: `PromptRegistry.DEFAULT_PROMPTS[prompt_type]`
### 6.5 Debug Panel Integration
**Console Logging:**
- `ConsoleStepTracker` logs to stdout/stderr (only if `DEBUG_MODE=True`)
- Logs include timestamps, phases, messages, errors
**Step Tracking:**
- `StepTracker` maintains `request_steps` and `response_steps` arrays
- Steps include: `stepNumber`, `stepName`, `status`, `message`, `duration`, `error`
- Steps are included in Celery task meta and displayed in progress modal
**Database Logging:**
- `AITaskLog` records all AI task executions
- Fields: `task_id`, `function_name`, `phase`, `status`, `cost`, `tokens`, `request_steps`, `response_steps`, `error`, `payload`, `result`
---
## 7. Identified Redundancies or Repetition
### 7.1 Duplicate Constants
**Location 1:** `ai/constants.py`
- `MODEL_RATES`, `IMAGE_MODEL_RATES`, `VALID_OPENAI_IMAGE_MODELS`, `VALID_SIZES_BY_MODEL`, `DEFAULT_AI_MODEL`, `JSON_MODE_MODELS`
**Location 2:** `utils/ai_processor.py` (lines 18-44)
- **EXACT DUPLICATE** of all constants from `constants.py`
**Impact:** Constants are defined in two places, risking inconsistency.
### 7.2 Duplicate JSON Extraction Logic
**Location 1:** `ai/ai_core.py::extract_json()` (lines 391-429)
- Handles markdown code blocks, multiline JSON, direct JSON
**Location 2:** `utils/ai_processor.py::_extract_json_from_response()` (lines 342-449)
- **MORE COMPREHENSIVE** - handles more edge cases, balanced brace matching
**Impact:** Two implementations with different capabilities. `ai_processor.py` version is more robust.
### 7.3 Duplicate OpenAI API Calling Logic
**Location 1:** `ai/ai_core.py::run_ai_request()` (lines 106-389)
- Centralized method with console logging via `ConsoleStepTracker`
- Handles validation, model selection, cost calculation
- Returns standardized dict format
**Location 2:** `utils/ai_processor.py::_call_openai()` (lines 125-340)
- **SIMILAR LOGIC** but with `response_steps` parameter instead of `tracker`
- Less comprehensive error handling
- Different return format
**Impact:** Two code paths for the same operation. New code should use `AICore.run_ai_request()`.
### 7.4 Duplicate Image Generation Logic
**Location 1:** `ai/ai_core.py::generate_image()` + `_generate_image_openai()` + `_generate_image_runware()` (lines 431-728)
- Uses `print()` statements for logging (inconsistent with console tracker)
**Location 2:** `utils/ai_processor.py::generate_image()` (lines 667-1043)
- **MORE COMPREHENSIVE** - extensive logging, better error handling
- Handles Runware authentication flow
**Impact:** Two implementations. `ai_processor.py` version has better logging.
### 7.5 Duplicate Prompt Loading Logic
**Location 1:** `ai/prompts.py::PromptRegistry.get_prompt()` (lines 280-333)
- Hierarchical resolution: task override → DB prompt → default
- Supports `[IGNY8_*]` placeholders and `{variable}` format
**Location 2:** `utils/ai_processor.py::get_prompt()` (lines 1044-1057)
- Simple database lookup via `modules/system/utils.get_prompt_value()`
- No hierarchical resolution
**Location 3:** Direct calls in `modules/writer/tasks.py` (lines 343, 959, 964)
- Uses `get_prompt_value()` and `get_default_prompt()` directly
**Impact:** Three different ways to load prompts. New code should use `PromptRegistry`.
### 7.6 Duplicate Model Configuration Logic
**Location 1:** `ai/settings.py::get_model_config()` (lines 49-97)
- Reads from `IntegrationSettings` if account provided
- Falls back to `MODEL_CONFIG` defaults
**Location 2:** `ai/ai_core.py::_load_account_settings()` (lines 46-90)
- Reads model from `IntegrationSettings` directly
- Similar logic but embedded in `AICore.__init__()`
**Location 3:** `utils/ai_processor.py::_get_model()` (lines 98-123)
- Reads model from `IntegrationSettings` directly
- Similar logic but embedded in `AIProcessor.__init__()`
**Impact:** Model loading logic duplicated in three places.
### 7.7 Duplicate API Key Loading Logic
**Location 1:** `ai/ai_core.py::_load_account_settings()` (lines 46-90)
- Loads OpenAI and Runware keys from `IntegrationSettings`
**Location 2:** `utils/ai_processor.py::_get_api_key()` (lines 73-96)
- **EXACT SAME LOGIC** for loading API keys
**Impact:** Identical code in two places.
### 7.8 Repeated Error Handling Patterns
**Pattern:** Multiple files have similar try/except blocks for:
- API request errors
- JSON parsing errors
- Database errors
- Validation errors
**Impact:** Error handling is not centralized, making it harder to maintain consistent error messages and logging.
### 7.9 Repeated Progress Update Patterns
**Pattern:** Multiple places manually build progress update dicts:
- `modules/writer/tasks.py` (lines 62-73, 220-231, etc.)
- `modules/planner/tasks.py` (lines 59-71, 203-215, etc.)
- `ai/engine.py` (lines 57, 79, 141, etc.)
**Impact:** Progress update format is not standardized, though `ProgressTracker` exists to handle this.
---
## 8. Summary of Potential Consolidation Areas
### 8.1 Constants Consolidation
**Observation:** Model rates, valid models, and configuration constants are duplicated between `ai/constants.py` and `utils/ai_processor.py`.
**Potential Action:** Remove constants from `ai_processor.py` and import from `constants.py`. However, `ai_processor.py` is marked as legacy, so this may not be necessary if it's being phased out.
### 8.2 JSON Extraction Consolidation
**Observation:** Two JSON extraction methods exist with different capabilities. `ai_processor.py::_extract_json_from_response()` is more comprehensive.
**Potential Action:** Enhance `AICore.extract_json()` with logic from `ai_processor.py`, or create a shared utility function.
### 8.3 API Request Consolidation
**Observation:** `AICore.run_ai_request()` and `AIProcessor._call_openai()` perform the same operation with different interfaces.
**Potential Action:** All new code should use `AICore.run_ai_request()`. Legacy code in `ai_processor.py` can remain for backward compatibility but should be marked as deprecated.
### 8.4 Image Generation Consolidation
**Observation:** Two image generation implementations exist. `ai_processor.py` version has better logging.
**Potential Action:** Enhance `AICore.generate_image()` with logging improvements from `ai_processor.py`, or migrate all code to use `AICore.generate_image()`.
### 8.5 Prompt Loading Consolidation
**Observation:** Three different methods exist for loading prompts: `PromptRegistry.get_prompt()`, `AIProcessor.get_prompt()`, and direct `get_prompt_value()` calls.
**Potential Action:** Migrate all code to use `PromptRegistry.get_prompt()` for consistency. Update legacy code paths gradually.
### 8.6 Model/API Key Loading Consolidation
**Observation:** Model and API key loading logic is duplicated in `AICore`, `AIProcessor`, and `settings.py`.
**Potential Action:** Create shared utility functions for loading settings from `IntegrationSettings`, used by all classes.
### 8.7 Error Handling Consolidation
**Observation:** Error handling patterns are repeated across multiple files.
**Potential Action:** Create centralized error handling utilities or enhance `AIEngine._handle_error()` to be more reusable.
### 8.8 Progress Tracking Consolidation
**Observation:** Some code manually builds progress update dicts instead of using `ProgressTracker`.
**Potential Action:** Migrate all progress updates to use `ProgressTracker.update()` for consistency.
### 8.9 Legacy Code Path Elimination
**Observation:** Multiple execution paths exist:
- New: `run_ai_task``AIEngine``BaseAIFunction` implementations
- Legacy: Direct `AIProcessor` calls in `modules/*/tasks.py`
**Potential Action:** Gradually migrate all legacy tasks to use the new framework. Mark legacy code as deprecated.
---
## 9. Assumptions Made
1. **File `wordpress.py`** was not read - assumed to be unrelated to AI processing based on name.
2. **Frontend code** was partially analyzed via search results - full frontend audit not performed.
3. **Database migrations** were not analyzed - assumed to be standard Django migrations.
4. **Test files** were not analyzed - `ai/tests/test_run.py` exists but was not read.
5. **Settings file** (`backend/igny8_core/settings.py`) was not read - assumed to contain standard Django settings.
6. **System module utilities** (`modules/system/utils.py`) were referenced but not fully read - assumed to contain `get_prompt_value()` and `get_default_prompt()` functions.
---
## 10. Appendix
### 10.1 File Line Counts
| Directory | Files | Total Lines |
|-----------|-------|-------------|
| `ai/` | 15 | ~2,500 |
| `ai/functions/` | 5 | ~1,300 |
| `utils/` (AI-related) | 3 | ~1,800 |
| `modules/planner/tasks.py` | 1 | 736 |
| `modules/writer/tasks.py` | 1 | 1,156 |
| **Total** | **25** | **~7,500** |
### 10.2 Function Count Summary
- **Core Framework Functions:** ~30
- **AI Function Implementations:** 4 classes × ~6 methods = ~24 methods
- **Tracking Functions:** ~20
- **Legacy Functions:** ~15
- **Celery Tasks:** 4
- **Total:** ~90 functions/methods
### 10.3 Key Design Patterns
1. **Template Method Pattern:** `BaseAIFunction` defines algorithm skeleton, subclasses implement steps
2. **Registry Pattern:** `FunctionRegistry` for dynamic function discovery
3. **Factory Pattern:** `get_function_instance()` creates function instances
4. **Strategy Pattern:** Different AI functions implement same interface
5. **Observer Pattern:** `ProgressTracker` updates Celery task state
6. **Facade Pattern:** `AICore` provides simplified interface to OpenAI/Runware APIs
---
**End of Report**

View File

@@ -1,649 +0,0 @@
# IGNY8 AI System Unification — Complete Migration Plan
**Date:** 2024-12-19
**Goal:** Unify all AI functions into single structure, remove all redundancy, implement checklist-style progress UI
**Estimated Time:** 5 stages, ~2-3 days total
---
## Overview
This migration plan unifies the IGNY8 AI system by:
1. Standardizing backend progress messages with input data
2. Implementing checklist-style progress UI with 3 states (pending/in-progress/completed)
3. Migrating all views to use unified `run_ai_task` entrypoint
4. Removing duplicate code and deprecated files
5. Final cleanup and verification
---
## Stage 1: Backend — Standardize Progress Messages with Input Data
**Goal:** Update `AIEngine` to send user-friendly messages with actual input data
**Files to Modify:** `backend/igny8_core/ai/engine.py`
**Estimated Time:** 1-2 hours
### Step 1.1: Add Helper Methods to AIEngine
**File:** `backend/igny8_core/ai/engine.py`
Add these helper methods to the `AIEngine` class (after `__init__` method):
```python
def _get_input_description(self, function_name: str, payload: dict, count: int) -> str:
"""Get user-friendly input description"""
if function_name == 'auto_cluster':
return f"{count} keyword{'s' if count != 1 else ''}"
elif function_name == 'generate_ideas':
return f"{count} cluster{'s' if count != 1 else ''}"
elif function_name == 'generate_content':
return f"{count} task{'s' if count != 1 else ''}"
elif function_name == 'generate_images':
return f"{count} task{'s' if count != 1 else ''}"
return f"{count} item{'s' if count != 1 else ''}"
def _get_prep_message(self, function_name: str, count: int, data: Any) -> str:
"""Get user-friendly prep message"""
if function_name == 'auto_cluster':
return f"Loading {count} keyword{'s' if count != 1 else ''}"
elif function_name == 'generate_ideas':
return f"Loading {count} cluster{'s' if count != 1 else ''}"
elif function_name == 'generate_content':
return f"Preparing {count} content idea{'s' if count != 1 else ''}"
elif function_name == 'generate_images':
return f"Extracting image prompts from {count} task{'s' if count != 1 else ''}"
return f"Preparing {count} item{'s' if count != 1 else ''}"
def _get_ai_call_message(self, function_name: str, count: int) -> str:
"""Get user-friendly AI call message"""
if function_name == 'auto_cluster':
return f"Grouping {count} keyword{'s' if count != 1 else ''} into clusters"
elif function_name == 'generate_ideas':
return f"Generating content ideas for {count} cluster{'s' if count != 1 else ''}"
elif function_name == 'generate_content':
return f"Writing article{'s' if count != 1 else ''} with AI"
elif function_name == 'generate_images':
return f"Creating image{'s' if count != 1 else ''} with AI"
return f"Processing with AI"
def _get_parse_message(self, function_name: str) -> str:
"""Get user-friendly parse message"""
if function_name == 'auto_cluster':
return "Organizing clusters"
elif function_name == 'generate_ideas':
return "Structuring outlines"
elif function_name == 'generate_content':
return "Formatting content"
elif function_name == 'generate_images':
return "Processing images"
return "Processing results"
def _get_save_message(self, function_name: str, count: int) -> str:
"""Get user-friendly save message"""
if function_name == 'auto_cluster':
return f"Saving {count} cluster{'s' if count != 1 else ''}"
elif function_name == 'generate_ideas':
return f"Saving {count} idea{'s' if count != 1 else ''}"
elif function_name == 'generate_content':
return f"Saving {count} article{'s' if count != 1 else ''}"
elif function_name == 'generate_images':
return f"Saving {count} image{'s' if count != 1 else ''}"
return f"Saving {count} item{'s' if count != 1 else ''}"
```
### Step 1.2: Update execute() Method to Use Helper Methods
**File:** `backend/igny8_core/ai/engine.py`
In the `execute()` method, replace step tracking messages:
**Replace lines 48-57 (INIT phase):**
```python
# OLD:
self.console_tracker.prep("Validating input payload")
validated = fn.validate(payload, self.account)
if not validated['valid']:
self.console_tracker.error('ValidationError', validated['error'])
return self._handle_error(validated['error'], fn)
self.console_tracker.prep("Validation complete")
self.step_tracker.add_request_step("INIT", "success", "Validation complete")
self.tracker.update("INIT", 10, "Validation complete", meta=self.step_tracker.get_meta())
# NEW:
# Extract input data for user-friendly messages
ids = payload.get('ids', [])
input_count = len(ids) if ids else 0
input_description = self._get_input_description(function_name, payload, input_count)
self.console_tracker.prep(f"Validating {input_description}")
validated = fn.validate(payload, self.account)
if not validated['valid']:
self.console_tracker.error('ValidationError', validated['error'])
return self._handle_error(validated['error'], fn)
validation_message = f"Validating {input_description}"
self.console_tracker.prep("Validation complete")
self.step_tracker.add_request_step("INIT", "success", validation_message)
self.tracker.update("INIT", 10, validation_message, meta=self.step_tracker.get_meta())
```
**Replace lines 59-79 (PREP phase):**
```python
# OLD:
self.console_tracker.prep("Loading data from database")
data = fn.prepare(payload, self.account)
# ... existing data_count logic ...
self.console_tracker.prep(f"Building prompt from {data_count} items")
prompt = fn.build_prompt(data, self.account)
self.console_tracker.prep(f"Prompt built: {len(prompt)} characters")
self.step_tracker.add_request_step("PREP", "success", f"Loaded {data_count} items, built prompt ({len(prompt)} chars)")
self.tracker.update("PREP", 25, f"Data prepared: {data_count} items", meta=self.step_tracker.get_meta())
# NEW:
prep_message = self._get_prep_message(function_name, input_count, payload)
self.console_tracker.prep(prep_message)
data = fn.prepare(payload, self.account)
# ... existing data_count logic ...
prompt = fn.build_prompt(data, self.account)
self.console_tracker.prep(f"Prompt built: {len(prompt)} characters")
self.step_tracker.add_request_step("PREP", "success", prep_message)
self.tracker.update("PREP", 25, prep_message, meta=self.step_tracker.get_meta())
```
**Replace lines 136-141 (AI_CALL phase):**
```python
# OLD:
self.step_tracker.add_response_step(
"AI_CALL",
"success",
f"Calling {model or 'default'} model..."
)
self.tracker.update("AI_CALL", 30, f"Sending to {model or 'default'}...", meta=self.step_tracker.get_meta())
# NEW:
ai_call_message = self._get_ai_call_message(function_name, data_count)
self.step_tracker.add_response_step("AI_CALL", "success", ai_call_message)
self.tracker.update("AI_CALL", 50, ai_call_message, meta=self.step_tracker.get_meta())
```
**Find PARSE phase (around line 200-210) and replace:**
```python
# OLD:
self.step_tracker.add_response_step("PARSE", "success", "Parsing response...")
self.tracker.update("PARSE", 70, "Parsing response...", meta=self.step_tracker.get_meta())
# NEW:
parse_message = self._get_parse_message(function_name)
self.step_tracker.add_response_step("PARSE", "success", parse_message)
self.tracker.update("PARSE", 70, parse_message, meta=self.step_tracker.get_meta())
```
**Find SAVE phase (around line 250-260) and replace:**
```python
# OLD:
self.step_tracker.add_response_step("SAVE", "success", "Saving results...")
self.tracker.update("SAVE", 85, "Saving results...", meta=self.step_tracker.get_meta())
# NEW:
save_message = self._get_save_message(function_name, data_count)
self.step_tracker.add_response_step("SAVE", "success", save_message)
self.tracker.update("SAVE", 85, save_message, meta=self.step_tracker.get_meta())
```
### Step 1.3: Remove Technical Debug Messages
**File:** `backend/igny8_core/ai/engine.py`
Remove or comment out lines 115-124 (model configuration tracking in step tracker):
```python
# REMOVE these lines (keep console logging, but not step tracker):
# self.step_tracker.add_request_step(
# "PREP",
# "success",
# f"AI model in settings: {model_from_integration or 'Not set'}"
# )
# self.step_tracker.add_request_step(
# "PREP",
# "success",
# f"AI model selected for request: {model or 'default'}"
# )
```
### Verification Checklist for Stage 1:
- [ ] Helper methods added to `AIEngine` class
- [ ] All phase messages updated to use helper methods
- [ ] Messages include actual input counts (e.g., "Validating 5 keywords")
- [ ] No technical terms like "database" or "parsing" in user-facing messages
- [ ] Test: Run `auto_cluster` and verify messages in step logs
---
## Stage 2: Frontend — Implement Checklist-Style Progress Modal
**Goal:** Replace progress bar with checklist UI showing 3 states (pending/in-progress/completed)
**Files to Modify:** `frontend/src/components/common/ProgressModal.tsx`
**Files to Create:** None
**Estimated Time:** 2-3 hours
### Step 2.1: Replace ProgressModal Component
**File:** `frontend/src/components/common/ProgressModal.tsx`
Replace the entire file with the new checklist-style implementation (see previous response for full code).
Key changes:
- Remove progress bar component
- Add checklist-style step display
- Add 3-state logic (pending/in-progress/completed)
- Add success alert in same modal when completed
- Use step logs to determine current phase
### Step 2.2: Update useProgressModal Hook (Simplify)
**File:** `frontend/src/hooks/useProgressModal.ts`
Remove all `aiRequestLogsStore` references:
- Remove lines 501-642 (all store-related code)
- Keep only polling logic and state management
- Simplify step mapping logic
### Verification Checklist for Stage 2:
- [ ] ProgressModal shows checklist instead of progress bar
- [ ] Steps show as pending (gray/disabled) initially
- [ ] Steps show as in-progress (blue/spinner) when active
- [ ] Steps show as completed (green/checkmark) when done
- [ ] Success alert appears in same modal when completed
- [ ] No errors in browser console
- [ ] Test: Run `auto_cluster` and verify checklist UI
---
## Stage 3: Migrate Views to Unified Entrypoint
**Goal:** Update all views to use `run_ai_task` instead of legacy task functions
**Files to Modify:**
- `backend/igny8_core/modules/writer/views.py`
- `backend/igny8_core/modules/planner/views.py` (verify already migrated)
**Estimated Time:** 2-3 hours
### Step 3.1: Migrate auto_generate_content View
**File:** `backend/igny8_core/modules/writer/views.py`
**Replace lines 180-228** (the entire try/except block for Celery task):
```python
# OLD:
from .tasks import auto_generate_content_task
if hasattr(auto_generate_content_task, 'delay'):
task = auto_generate_content_task.delay(ids, account_id=account_id)
# ... rest of old code
# NEW:
from igny8_core.ai.tasks import run_ai_task
from kombu.exceptions import OperationalError as KombuOperationalError
try:
if hasattr(run_ai_task, 'delay'):
task = run_ai_task.delay(
function_name='generate_content',
payload={'ids': ids},
account_id=account_id
)
logger.info(f"Task queued: {task.id}")
return Response({
'success': True,
'task_id': str(task.id),
'message': 'Content generation started'
}, status=status.HTTP_200_OK)
else:
# Celery not available - execute synchronously
logger.info("auto_generate_content: Executing synchronously (Celery not available)")
result = run_ai_task(
function_name='generate_content',
payload={'ids': ids},
account_id=account_id
)
if result.get('success'):
return Response({
'success': True,
'tasks_updated': result.get('count', 0),
'message': 'Content generated successfully'
}, status=status.HTTP_200_OK)
else:
return Response({
'error': result.get('error', 'Content generation failed'),
'type': 'TaskExecutionError'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
except KombuOperationalError as e:
logger.error(f"Celery connection error: {str(e)}")
return Response({
'error': 'Task queue unavailable. Please try again.',
'type': 'QueueError'
}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
except Exception as e:
logger.error(f"Error queuing content generation task: {str(e)}", exc_info=True)
return Response({
'error': f'Failed to start content generation: {str(e)}',
'type': 'TaskError'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
```
### Step 3.2: Migrate auto_generate_images View
**File:** `backend/igny8_core/modules/writer/views.py`
**Replace lines 358-377** (the entire try/except block):
```python
# OLD:
from .tasks import auto_generate_images_task
if hasattr(auto_generate_images_task, 'delay'):
task = auto_generate_images_task.delay(task_ids, account_id=account_id)
# ... rest of old code
# NEW:
from igny8_core.ai.tasks import run_ai_task
from kombu.exceptions import OperationalError as KombuOperationalError
try:
if hasattr(run_ai_task, 'delay'):
task = run_ai_task.delay(
function_name='generate_images',
payload={'ids': task_ids},
account_id=account_id
)
return Response({
'success': True,
'task_id': str(task.id),
'message': 'Image generation started'
}, status=status.HTTP_200_OK)
else:
# Celery not available - execute synchronously
result = run_ai_task(
function_name='generate_images',
payload={'ids': task_ids},
account_id=account_id
)
if result.get('success'):
return Response({
'success': True,
'images_created': result.get('count', 0),
'message': result.get('message', 'Image generation completed')
}, status=status.HTTP_200_OK)
else:
return Response({
'error': result.get('error', 'Image generation failed'),
'type': 'TaskExecutionError'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
except KombuOperationalError as e:
logger.error(f"Celery connection error: {str(e)}")
return Response({
'error': 'Task queue unavailable. Please try again.',
'type': 'QueueError'
}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
except Exception as e:
logger.error(f"Error queuing image generation task: {str(e)}", exc_info=True)
return Response({
'error': f'Failed to start image generation: {str(e)}',
'type': 'TaskError'
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
```
### Step 3.3: Verify Planner Views Already Migrated
**File:** `backend/igny8_core/modules/planner/views.py`
Verify that `auto_cluster` and `auto_generate_ideas` already use `run_ai_task`:
- [ ] `auto_cluster` uses `run_ai_task` (line 480)
- [ ] `auto_generate_ideas` uses `run_ai_task` (line 756)
### Verification Checklist for Stage 3:
- [ ] `auto_generate_content` uses `run_ai_task`
- [ ] `auto_generate_images` uses `run_ai_task`
- [ ] All views return consistent response format
- [ ] Error handling is consistent
- [ ] Test: Generate content and verify it works
- [ ] Test: Generate images and verify it works
---
## Stage 4: Remove Duplicate Code and Deprecated Files
**Goal:** Clean up duplicate constants, remove deprecated files, simplify code
**Files to Delete:** 6 files
**Files to Modify:** 5 files
**Estimated Time:** 2-3 hours
### Step 4.1: Remove Duplicate Constants
**File:** `backend/igny8_core/utils/ai_processor.py`
**Replace lines 18-44** (duplicate constants):
```python
# OLD:
MODEL_RATES = { ... }
IMAGE_MODEL_RATES = { ... }
VALID_OPENAI_IMAGE_MODELS = { ... }
VALID_SIZES_BY_MODEL = { ... }
# NEW:
from igny8_core.ai.constants import (
MODEL_RATES,
IMAGE_MODEL_RATES,
VALID_OPENAI_IMAGE_MODELS,
VALID_SIZES_BY_MODEL,
DEFAULT_AI_MODEL,
JSON_MODE_MODELS,
)
```
### Step 4.2: Remove response_steps Parameter
**File:** `backend/igny8_core/utils/ai_processor.py`
Remove `response_steps` parameter from all methods:
- Find all method signatures with `response_steps=None` (lines 1064, 1135, etc.)
- Remove the parameter
- Remove all `response_steps.append()` calls (lines 1135-1299)
- Keep the file (still used by legacy code temporarily)
### Step 4.3: Simplify task_progress Endpoint
**File:** `backend/igny8_core/modules/system/integration_views.py`
**Simplify `task_progress()` method** (lines 734-1163):
Replace complex extraction logic with simple meta retrieval:
```python
# In task_progress method, replace lines 784-1100 with:
meta = {}
request_steps = []
response_steps = []
try:
if hasattr(task, 'info') and task.info:
if isinstance(task.info, dict):
meta = task.info.get('meta', {})
if isinstance(meta, dict):
request_steps = meta.get('request_steps', [])
response_steps = meta.get('response_steps', [])
except Exception as e:
logger.debug(f"Error extracting meta: {str(e)}")
# Use request_steps and response_steps in response
```
### Step 4.4: Remove Deprecated Store References
**File:** `frontend/src/services/api.ts`
Remove all `aiRequestLogsStore` imports and references:
- Remove line 4: `import { useAIRequestLogsStore } from '../store/aiRequestLogsStore';`
- Remove lines 579, 601, 671-672, 730-731, 812-813, 1185-1186, 1265-1266 (all store references)
**File:** `frontend/src/hooks/useProgressModal.ts`
Remove all `aiRequestLogsStore` references (lines 501-642)
**File:** `frontend/src/templates/TablePageTemplate.tsx`
Remove commented import (line 44)
### Step 4.5: Delete Deprecated Files
**Delete these files:**
1. `backend/igny8_core/modules/planner/tasks.py`
- Already deprecated, no longer used
2. `backend/igny8_core/modules/writer/tasks.py`
- No longer used after Stage 3 migration
3. `backend/igny8_core/ai/processor.py`
- Deprecated wrapper, redirects to AICore
4. `frontend/src/store/aiRequestLogsStore.ts`
- Deprecated debug store
5. `frontend/src/components/debug/ResourceDebugOverlay.tsx`
- Optional: Delete if not needed
6. `frontend/src/components/debug/ResourceDebugToggle.tsx`
- Optional: Delete if not needed (or keep if still used)
### Verification Checklist for Stage 4:
- [ ] Duplicate constants removed from `ai_processor.py`
- [ ] `response_steps` parameter removed from all methods
- [ ] `task_progress` endpoint simplified
- [ ] All deprecated store references removed
- [ ] Deprecated files deleted
- [ ] No import errors after deletions
- [ ] Test: Verify all AI functions still work
---
## Stage 5: Final Cleanup and Verification
**Goal:** Final testing, documentation, and cleanup
**Estimated Time:** 1-2 hours
### Step 5.1: Remove Debug Overlay from Layout (if deleted)
**File:** `frontend/src/layout/AppLayout.tsx`
If you deleted debug components, remove:
- Line 12: `import ResourceDebugOverlay from "../components/debug/ResourceDebugOverlay";`
- Lines 166-180: Debug toggle listener
- Lines 197-198: `<ResourceDebugOverlay enabled={debugEnabled} />`
### Step 5.2: Update Function Metadata
**File:** `backend/igny8_core/ai/functions/auto_cluster.py`
Verify `get_metadata()` returns correct phase messages (should already be correct)
**File:** `backend/igny8_core/ai/functions/generate_ideas.py`
Verify `get_metadata()` returns correct phase messages
**File:** `backend/igny8_core/ai/functions/generate_content.py`
Verify `get_metadata()` returns correct phase messages
**File:** `backend/igny8_core/ai/functions/generate_images.py`
Verify `get_metadata()` returns correct phase messages
### Step 5.3: Comprehensive Testing
Test each AI function end-to-end:
**Test 1: Keyword Clustering**
- [ ] Select 5-10 keywords
- [ ] Click "Auto Cluster"
- [ ] Verify checklist shows: "Validating 5 keywords" → "Loading 5 keywords" → etc.
- [ ] Verify success message: "Clustering complete — keywords grouped into meaningful clusters."
- [ ] Verify clusters created in database
**Test 2: Idea Generation**
- [ ] Select 1-2 clusters
- [ ] Click "Generate Ideas"
- [ ] Verify checklist shows correct steps
- [ ] Verify success message: "Content ideas and outlines created successfully."
- [ ] Verify ideas created in database
**Test 3: Content Generation**
- [ ] Select 1-2 tasks
- [ ] Click "Generate Content"
- [ ] Verify checklist shows correct steps
- [ ] Verify success message: "Article drafted successfully."
- [ ] Verify content saved to tasks
**Test 4: Image Generation**
- [ ] Select 1-2 tasks with content
- [ ] Click "Generate Images"
- [ ] Verify checklist shows correct steps
- [ ] Verify success message: "Images created and saved successfully."
- [ ] Verify images created in database
### Step 5.4: Code Review Checklist
- [ ] All AI functions use `run_ai_task` entrypoint
- [ ] All progress messages include input data
- [ ] No duplicate constants
- [ ] No deprecated code references
- [ ] Frontend shows checklist UI correctly
- [ ] Success messages appear in modal
- [ ] No console errors
- [ ] No TypeScript errors
- [ ] No Python linting errors
### Step 5.5: Documentation Update
Update any relevant documentation:
- [ ] Update API documentation if needed
- [ ] Update developer guide if needed
- [ ] Mark this migration as complete
---
## Rollback Plan
If issues occur during migration:
1. **Stage 1-2 Issues:** Revert `engine.py` and `ProgressModal.tsx` changes
2. **Stage 3 Issues:** Revert view changes, keep using legacy tasks temporarily
3. **Stage 4 Issues:** Restore deleted files from git history
4. **Stage 5 Issues:** Fix specific issues without rolling back
---
## Success Criteria
Migration is complete when:
- ✅ All AI functions use unified `run_ai_task` entrypoint
- ✅ All progress messages are user-friendly with input data
- ✅ Frontend shows checklist-style progress UI
- ✅ Success messages appear in modal
- ✅ No duplicate code remains
- ✅ All deprecated files deleted
- ✅ All tests pass
- ✅ No console/terminal errors
---
## Notes
- **Backward Compatibility:** Legacy code in `utils/ai_processor.py` is kept temporarily for any remaining references
- **Debug Components:** ResourceDebugOverlay can be kept if still needed for other debugging
- **Testing:** Test each stage before moving to next stage
- **Git:** Commit after each stage for easy rollback
---
**End of Migration Plan**

View File

@@ -1,271 +0,0 @@
# AI Function Related Files
This document lists all files containing code related to:
- Auto Cluster Keywords
- Auto Generate Ideas
- Auto Generate Content
- Auto Generate Images
---
## Backend Files
### Auto Cluster Keywords
#### Core Implementation
- `backend/igny8_core/ai/functions/auto_cluster.py` - **Main AI function implementation** (BaseAIFunction)
- `backend/igny8_core/ai/base.py` - Base AI function class
- `backend/igny8_core/ai/engine.py` - AI engine orchestrator
- `backend/igny8_core/ai/processor.py` - AI processor wrapper
- `backend/igny8_core/ai/tasks.py` - Unified Celery task entrypoint
- `backend/igny8_core/ai/registry.py` - Function registry
- `backend/igny8_core/ai/tracker.py` - Progress and cost tracking
#### API Endpoints & Views
- `backend/igny8_core/modules/planner/views.py` - **KeywordViewSet.auto_cluster()** action
- `backend/igny8_core/modules/planner/urls.py` - URL routing
#### Celery Tasks (Legacy/Alternative)
- `backend/igny8_core/modules/planner/tasks.py` - **auto_cluster_keywords_task()** (legacy implementation)
#### AI Processor
- `backend/igny8_core/utils/ai_processor.py` - **AIProcessor.cluster_keywords()** method
#### Models
- `backend/igny8_core/modules/planner/models.py` - Keywords, Clusters models
#### Serializers
- `backend/igny8_core/modules/planner/serializers.py` - Keyword, Cluster serializers
- `backend/igny8_core/modules/planner/cluster_serializers.py` - Cluster-specific serializers
#### System Integration
- `backend/igny8_core/modules/system/schemas.py` - Schema definitions
- `backend/igny8_core/modules/system/utils.py` - Prompt loading utilities
---
### Auto Generate Ideas
#### Core Implementation
- `backend/igny8_core/utils/ai_processor.py` - **AIProcessor.generate_ideas()** method
#### API Endpoints & Views
- `backend/igny8_core/modules/planner/views.py` - **ClusterViewSet.auto_generate_ideas()** action
- `backend/igny8_core/modules/planner/urls.py` - URL routing
#### Celery Tasks
- `backend/igny8_core/modules/planner/tasks.py` - **auto_generate_ideas_task()** and **generate_single_idea_core()**
#### Models
- `backend/igny8_core/modules/planner/models.py` - Clusters, ContentIdeas models
#### Serializers
- `backend/igny8_core/modules/planner/serializers.py` - Cluster, ContentIdeas serializers
#### System Integration
- `backend/igny8_core/modules/system/utils.py` - Prompt loading utilities
---
### Auto Generate Content
#### Core Implementation
- `backend/igny8_core/utils/ai_processor.py` - **AIProcessor.generate_content()** method
#### API Endpoints & Views
- `backend/igny8_core/modules/writer/views.py` - **TasksViewSet.auto_generate_content()** action
- `backend/igny8_core/modules/writer/urls.py` - URL routing
#### Celery Tasks
- `backend/igny8_core/modules/writer/tasks.py` - **auto_generate_content_task()**
#### Models
- `backend/igny8_core/modules/writer/models.py` - Tasks, Content models
#### Serializers
- `backend/igny8_core/modules/writer/serializers.py` - Task, Content serializers
#### System Integration
- `backend/igny8_core/modules/system/schemas.py` - Schema definitions
- `backend/igny8_core/modules/system/utils.py` - Prompt loading utilities
---
### Auto Generate Images
#### Core Implementation
- `backend/igny8_core/utils/ai_processor.py` - **AIProcessor.extract_image_prompts()** and **AIProcessor.generate_image()** methods
#### API Endpoints & Views
- `backend/igny8_core/modules/writer/views.py` - **TasksViewSet.auto_generate_images()** action
- `backend/igny8_core/modules/writer/urls.py` - URL routing
#### Celery Tasks
- `backend/igny8_core/modules/writer/tasks.py` - **auto_generate_images_task()**
#### Models
- `backend/igny8_core/modules/writer/models.py` - Tasks, Images models
#### Serializers
- `backend/igny8_core/modules/writer/serializers.py` - Task, Images serializers
#### System Integration
- `backend/igny8_core/modules/system/schemas.py` - Schema definitions
- `backend/igny8_core/modules/system/integration_views.py` - Integration settings for image generation
---
## Frontend Files
### Auto Cluster Keywords
#### Pages
- `frontend/src/pages/Planner/Keywords.tsx` - **Main page component** with auto cluster functionality
#### API Services
- `frontend/src/services/api.ts` - **autoClusterKeywords()** function
#### Configuration
- `frontend/src/config/pages/keywords.config.tsx` - Page configuration
- `frontend/src/config/pages/table-actions.config.tsx` - Action button configurations
#### State Management
- `frontend/src/store/aiRequestLogsStore.ts` - AI request/response logs store
#### Hooks
- `frontend/src/hooks/useProgressModal.ts` - Progress modal hook for tracking task progress
---
### Auto Generate Ideas
#### Pages
- `frontend/src/pages/Planner/Clusters.tsx` - **Main page component** with auto generate ideas functionality
#### API Services
- `frontend/src/services/api.ts` - **autoGenerateIdeas()** function
#### Configuration
- `frontend/src/config/pages/clusters.config.tsx` - Page configuration
- `frontend/src/config/pages/table-actions.config.tsx` - Action button configurations
#### State Management
- `frontend/src/store/aiRequestLogsStore.ts` - AI request/response logs store
#### Hooks
- `frontend/src/hooks/useProgressModal.ts` - Progress modal hook for tracking task progress
---
### Auto Generate Content
#### Pages
- `frontend/src/pages/Writer/Tasks.tsx` - **Main page component** with auto generate content functionality
#### API Services
- `frontend/src/services/api.ts` - **autoGenerateContent()** function
#### Configuration
- `frontend/src/config/pages/tasks.config.tsx` - Page configuration
- `frontend/src/config/pages/table-actions.config.tsx` - Action button configurations
#### State Management
- `frontend/src/store/aiRequestLogsStore.ts` - AI request/response logs store
#### Hooks
- `frontend/src/hooks/useProgressModal.ts` - Progress modal hook for tracking task progress
---
### Auto Generate Images
#### Pages
- `frontend/src/pages/Writer/Tasks.tsx` - **Main page component** with auto generate images functionality
#### API Services
- `frontend/src/services/api.ts` - **autoGenerateImages()** function
#### Configuration
- `frontend/src/config/pages/tasks.config.tsx` - Page configuration
- `frontend/src/config/pages/images.config.tsx` - Image-related configuration
- `frontend/src/config/pages/table-actions.config.tsx` - Action button configurations
#### State Management
- `frontend/src/store/aiRequestLogsStore.ts` - AI request/response logs store
#### Hooks
- `frontend/src/hooks/useProgressModal.ts` - Progress modal hook for tracking task progress
---
## Shared/Common Files
### AI Framework (Backend)
- `backend/igny8_core/ai/__init__.py` - AI module initialization and auto-registration
- `backend/igny8_core/ai/models.py` - AITaskLog model for unified logging
- `backend/igny8_core/ai/types.py` - Shared dataclasses and types
- `backend/igny8_core/ai/admin.py` - Admin interface for AITaskLog
### Progress Tracking (Backend)
- `backend/igny8_core/modules/system/integration_views.py` - **task_progress()** endpoint
### Progress Tracking (Frontend)
- `frontend/src/components/common/ProgressModal.tsx` - Progress modal component
- `frontend/src/hooks/useProgressModal.ts` - Progress modal hook
### Common Components (Frontend)
- `frontend/src/components/common/FormModal.tsx` - Form modal used in AI function pages
---
## Summary by Function
### Auto Cluster Keywords
**Backend**: 15 files
**Frontend**: 5 files
**Total**: 20 files
### Auto Generate Ideas
**Backend**: 7 files
**Frontend**: 5 files
**Total**: 12 files
### Auto Generate Content
**Backend**: 7 files
**Frontend**: 5 files
**Total**: 12 files
### Auto Generate Images
**Backend**: 7 files
**Frontend**: 5 files
**Total**: 12 files
### Shared/Common
**Backend**: 4 files
**Frontend**: 3 files
**Total**: 7 files
---
## Key Files (Most Important)
### Backend Core Files
1. `backend/igny8_core/ai/functions/auto_cluster.py` - Auto cluster AI function
2. `backend/igny8_core/utils/ai_processor.py` - Unified AI processor (all functions)
3. `backend/igny8_core/modules/planner/views.py` - Auto cluster & ideas API endpoints
4. `backend/igny8_core/modules/writer/views.py` - Content & images API endpoints
5. `backend/igny8_core/modules/planner/tasks.py` - Planner Celery tasks
6. `backend/igny8_core/modules/writer/tasks.py` - Writer Celery tasks
7. `backend/igny8_core/ai/tasks.py` - Unified AI task entrypoint
### Frontend Core Files
1. `frontend/src/pages/Planner/Keywords.tsx` - Auto cluster UI
2. `frontend/src/pages/Planner/Clusters.tsx` - Auto generate ideas UI
3. `frontend/src/pages/Writer/Tasks.tsx` - Content & images generation UI
4. `frontend/src/services/api.ts` - All API functions
5. `frontend/src/hooks/useProgressModal.ts` - Progress tracking hook
---
**Last Updated**: 2025-01-XX

View File

@@ -1,274 +0,0 @@
# IGNY8 AI Framework Documentation
**Version:** 1.0
**Last Updated:** 2025-01-XX
**Purpose:** Complete documentation of the unified AI framework architecture.
---
## Overview
The IGNY8 AI Framework provides a unified, consistent architecture for all AI functions. It eliminates code duplication, standardizes progress tracking, and provides a single interface for all AI operations.
### Key Benefits
- **90% Code Reduction**: Functions are now ~100 lines instead of ~600
- **Consistent UX**: All functions use the same progress modal and tracking
- **Unified Logging**: Single `AITaskLog` table for all AI operations
- **Easy Extension**: Add new functions by creating one class
- **Better Debugging**: Detailed step-by-step tracking for all operations
---
## Architecture
### Directory Structure
```
igny8_core/ai/
├── __init__.py # Auto-registers all functions
├── apps.py # Django app configuration
├── admin.py # Admin interface for AITaskLog
├── base.py # BaseAIFunction abstract class
├── engine.py # AIEngine orchestrator
├── processor.py # AIProcessor wrapper
├── registry.py # Function registry
├── tracker.py # StepTracker, ProgressTracker, CostTracker
├── tasks.py # Unified Celery task entrypoint
├── types.py # Shared dataclasses
├── models.py # AITaskLog model
└── functions/ # Function implementations
├── __init__.py
└── auto_cluster.py # Auto cluster function
```
---
## Core Components
### 1. BaseAIFunction
Abstract base class that all AI functions inherit from.
**Methods to implement:**
- `get_name()`: Return function name
- `prepare()`: Load and prepare data
- `build_prompt()`: Build AI prompt
- `parse_response()`: Parse AI response
- `save_output()`: Save results to database
**Optional overrides:**
- `validate()`: Custom validation
- `get_max_items()`: Set item limit
- `get_model()`: Specify AI model
- `get_metadata()`: Function metadata
### 2. AIEngine
Central orchestrator that manages the execution pipeline.
**Phases:**
- INIT (0-10%): Validation & setup
- PREP (10-25%): Data loading & prompt building
- AI_CALL (25-60%): API call to provider
- PARSE (60-80%): Response parsing
- SAVE (80-95%): Database operations
- DONE (95-100%): Finalization
### 3. Function Registry
Dynamic function discovery system.
**Usage:**
```python
from igny8_core.ai.registry import register_function, get_function
# Register function
register_function('auto_cluster', AutoClusterFunction)
# Get function
fn = get_function('auto_cluster')
```
### 4. Unified Celery Task
Single entrypoint for all AI functions.
**Endpoint:** `run_ai_task(function_name, payload, account_id)`
**Example:**
```python
from igny8_core.ai.tasks import run_ai_task
task = run_ai_task.delay(
function_name='auto_cluster',
payload={'ids': [1, 2, 3], 'sector_id': 1},
account_id=1
)
```
---
## Function Implementation Example
### Auto Cluster Function
```python
from igny8_core.ai.base import BaseAIFunction
class AutoClusterFunction(BaseAIFunction):
def get_name(self) -> str:
return 'auto_cluster'
def get_max_items(self) -> int:
return 20
def prepare(self, payload: dict, account=None) -> Dict:
# Load keywords
ids = payload.get('ids', [])
keywords = Keywords.objects.filter(id__in=ids)
return {'keywords': keywords, ...}
def build_prompt(self, data: Dict, account=None) -> str:
# Build clustering prompt
return prompt_template.replace('[IGNY8_KEYWORDS]', keywords_text)
def parse_response(self, response: str, step_tracker=None) -> List[Dict]:
# Parse AI response
return clusters
def save_output(self, parsed, original_data, account, progress_tracker) -> Dict:
# Save clusters to database
return {'clusters_created': 5, 'keywords_updated': 20}
```
---
## API Endpoint Example
### Before (Old): ~300 lines
### After (New): ~50 lines
```python
@action(detail=False, methods=['post'], url_path='auto_cluster')
def auto_cluster(self, request):
from igny8_core.ai.tasks import run_ai_task
account = getattr(request, 'account', None)
account_id = account.id if account else None
payload = {
'ids': request.data.get('ids', []),
'sector_id': request.data.get('sector_id')
}
task = run_ai_task.delay(
function_name='auto_cluster',
payload=payload,
account_id=account_id
)
return Response({
'success': True,
'task_id': str(task.id),
'message': 'Clustering started'
})
```
---
## Progress Tracking
### Unified Progress Endpoint
**URL:** `/api/v1/system/settings/task_progress/<task_id>/`
**Response:**
```json
{
"state": "PROGRESS",
"meta": {
"phase": "AI_CALL",
"percentage": 45,
"message": "Analyzing keyword relationships...",
"request_steps": [...],
"response_steps": [...],
"cost": 0.000123,
"tokens": 1500
}
}
```
### Frontend Integration
All AI functions use the same progress modal:
- Single `useProgressModal` hook
- Unified progress endpoint
- Consistent phase labels
- Step-by-step logs
---
## Database Logging
### AITaskLog Model
Unified logging table for all AI operations.
**Fields:**
- `task_id`: Celery task ID
- `function_name`: Function name
- `account`: Account (required)
- `phase`: Current phase
- `status`: success/error/pending
- `cost`: API cost
- `tokens`: Token usage
- `request_steps`: Request step logs
- `response_steps`: Response step logs
- `error`: Error message (if any)
---
## Migration Guide
### Migrating Existing Functions
1. Create function class inheriting `BaseAIFunction`
2. Implement required methods
3. Register function in `ai/__init__.py`
4. Update API endpoint to use `run_ai_task`
5. Test and remove old code
### Example Migration
**Old code:**
```python
@action(...)
def auto_cluster(self, request):
# 300 lines of code
```
**New code:**
```python
@action(...)
def auto_cluster(self, request):
# 20 lines using framework
```
---
## Summary
The AI Framework provides:
1. **Unified Architecture**: Single framework for all AI functions
2. **Code Reduction**: 90% less code per function
3. **Consistent UX**: Same progress modal for all functions
4. **Better Debugging**: Detailed step tracking
5. **Easy Extension**: Add functions quickly
6. **Unified Logging**: Single log table
7. **Cost Tracking**: Automatic cost calculation
This architecture ensures maintainability, consistency, and extensibility while dramatically reducing code duplication.

View File

@@ -1,191 +0,0 @@
# Stage 1 - AI Folder Structure & Functional Split - COMPLETE ✅
## Summary
Successfully reorganized the AI backend into a clean, modular structure where every AI function lives inside its own file within `/ai/functions/`.
## ✅ Completed Deliverables
### 1. Folder Structure Created
```
backend/igny8_core/ai/
├── functions/
│ ├── __init__.py ✅
│ ├── auto_cluster.py ✅
│ ├── generate_ideas.py ✅
│ ├── generate_content.py ✅
│ └── generate_images.py ✅
├── ai_core.py ✅ (Shared operations)
├── validators.py ✅ (Consolidated validation)
├── constants.py ✅ (Model pricing, valid models)
├── engine.py ✅ (Updated to use AICore)
├── tracker.py ✅ (Existing)
├── base.py ✅ (Existing)
├── processor.py ✅ (Existing wrapper)
├── registry.py ✅ (Updated with new functions)
└── __init__.py ✅ (Updated exports)
```
### 2. Shared Modules Created
#### `ai_core.py`
- **Purpose**: Shared operations for all AI functions
- **Features**:
- API call construction (`call_openai`)
- Model selection (`get_model`, `get_api_key`)
- Response parsing (`extract_json`)
- Image generation (`generate_image`)
- Cost calculation (`calculate_cost`)
- **Status**: ✅ Complete
#### `validators.py`
- **Purpose**: Consolidated validation logic
- **Functions**:
- `validate_ids()` - Base ID validation
- `validate_keywords_exist()` - Keyword existence check
- `validate_cluster_limits()` - Plan limit checks
- `validate_cluster_exists()` - Cluster existence
- `validate_tasks_exist()` - Task existence
- `validate_api_key()` - API key validation
- `validate_model()` - Model validation
- `validate_image_size()` - Image size validation
- **Status**: ✅ Complete
#### `constants.py`
- **Purpose**: AI-related constants
- **Constants**:
- `MODEL_RATES` - Text model pricing
- `IMAGE_MODEL_RATES` - Image model pricing
- `VALID_OPENAI_IMAGE_MODELS` - Valid image models
- `VALID_SIZES_BY_MODEL` - Valid sizes per model
- `DEFAULT_AI_MODEL` - Default model name
- `JSON_MODE_MODELS` - Models supporting JSON mode
- **Status**: ✅ Complete
### 3. Function Files Created
#### `functions/auto_cluster.py`
- **Status**: ✅ Updated to use new validators and AICore
- **Changes**:
- Uses `validate_ids()`, `validate_keywords_exist()`, `validate_cluster_limits()` from validators
- Uses `AICore.extract_json()` for JSON parsing
- Maintains backward compatibility
#### `functions/generate_ideas.py`
- **Status**: ✅ Created
- **Features**:
- `GenerateIdeasFunction` class (BaseAIFunction)
- `generate_ideas_core()` legacy function for backward compatibility
- Uses AICore for API calls
- Uses validators for validation
#### `functions/generate_content.py`
- **Status**: ✅ Created
- **Features**:
- `GenerateContentFunction` class (BaseAIFunction)
- `generate_content_core()` legacy function for backward compatibility
- Uses AICore for API calls
- Uses validators for validation
#### `functions/generate_images.py`
- **Status**: ✅ Created
- **Features**:
- `GenerateImagesFunction` class (BaseAIFunction)
- `generate_images_core()` legacy function for backward compatibility
- Uses AICore for image generation
- Uses validators for validation
### 4. Import Paths Updated
#### Updated Files:
-`modules/planner/views.py` - Uses `generate_ideas_core` from new location
-`modules/planner/tasks.py` - Imports `generate_ideas_core` from new location
-`modules/writer/tasks.py` - Imports `generate_content_core` and `generate_images_core` from new locations
-`ai/engine.py` - Uses `AICore` instead of `AIProcessor`
-`ai/functions/auto_cluster.py` - Uses new validators and AICore
-`ai/registry.py` - Registered all new functions
-`ai/__init__.py` - Exports all new modules
### 5. Dependencies Verified
#### No Circular Dependencies ✅
- Functions depend on: `ai_core`, `validators`, `constants`, `base`
- `ai_core` depends on: `utils.ai_processor` (legacy, will be refactored later)
- `validators` depends on: `constants`, models
- `engine` depends on: `ai_core`, `base`, `tracker`
- All imports are clean and modular
#### Modular Structure ✅
- Each function file is self-contained
- Shared logic in `ai_core.py`
- Validation logic in `validators.py`
- Constants in `constants.py`
- No scattered or duplicated logic
## 📋 File Structure Details
### Core AI Modules
| File | Purpose | Dependencies |
|------|---------|--------------|
| `ai_core.py` | Shared AI operations | `utils.ai_processor` (legacy) |
| `validators.py` | All validation logic | `constants`, models |
| `constants.py` | AI constants | None |
| `engine.py` | Execution orchestrator | `ai_core`, `base`, `tracker` |
| `base.py` | Base function class | None |
| `tracker.py` | Progress/step tracking | None |
| `registry.py` | Function registry | `base`, function modules |
### Function Files
| File | Function Class | Legacy Function | Status |
|------|----------------|-----------------|--------|
| `auto_cluster.py` | `AutoClusterFunction` | N/A (uses engine) | ✅ Updated |
| `generate_ideas.py` | `GenerateIdeasFunction` | `generate_ideas_core()` | ✅ Created |
| `generate_content.py` | `GenerateContentFunction` | `generate_content_core()` | ✅ Created |
| `generate_images.py` | `GenerateImagesFunction` | `generate_images_core()` | ✅ Created |
## 🔄 Import Path Changes
### Old Imports (Still work, but deprecated)
```python
from igny8_core.utils.ai_processor import AIProcessor
from igny8_core.modules.planner.tasks import _generate_single_idea_core
```
### New Imports (Recommended)
```python
from igny8_core.ai.functions.generate_ideas import generate_ideas_core
from igny8_core.ai.functions.generate_content import generate_content_core
from igny8_core.ai.functions.generate_images import generate_images_core
from igny8_core.ai.ai_core import AICore
from igny8_core.ai.validators import validate_ids, validate_cluster_limits
from igny8_core.ai.constants import MODEL_RATES, DEFAULT_AI_MODEL
```
## ✅ Verification Checklist
- [x] All function files created in `ai/functions/`
- [x] Shared modules (`ai_core`, `validators`, `constants`) created
- [x] No circular dependencies
- [x] All imports updated in views and tasks
- [x] Functions registered in registry
- [x] `__init__.py` files updated
- [x] Backward compatibility maintained (legacy functions still work)
- [x] No linting errors
- [x] Structure matches required layout
## 🎯 Next Steps (Future Stages)
- **Stage 2**: Inject tracker into all functions
- **Stage 3**: Simplify logging
- **Stage 4**: Clean up legacy code
## 📝 Notes
- Legacy `AIProcessor` from `utils.ai_processor` is still used by `ai_core.py` as a wrapper
- This will be refactored in later stages
- All existing API endpoints continue to work
- No functional changes - only structural reorganization

View File

@@ -1,220 +0,0 @@
# Stage 2 - AI Execution & Logging Layer - COMPLETE ✅
## Summary
Successfully created a centralized, consistent, and traceable execution layer for all AI requests with unified request handler and clean console-based logging.
## ✅ Completed Deliverables
### 1. Centralized Execution in `ai_core.py`
#### `run_ai_request()` Method
- **Purpose**: Single entry point for all AI text generation requests
- **Features**:
- Step-by-step console logging with `print()` statements
- Standardized request payload construction
- Error handling with detailed logging
- Token counting and cost calculation
- Rate limit detection and logging
- Timeout handling
- JSON mode auto-enablement for supported models
#### Console Logging Format
```
[AI][function_name] Step 1: Preparing request...
[AI][function_name] Step 2: Using model: gpt-4o
[AI][function_name] Step 3: Auto-enabled JSON mode for gpt-4o
[AI][function_name] Step 4: Prompt length: 1234 characters
[AI][function_name] Step 5: Request payload prepared (model=gpt-4o, max_tokens=4000, temp=0.7)
[AI][function_name] Step 6: Sending request to OpenAI API...
[AI][function_name] Step 7: Received response in 2.34s (status=200)
[AI][function_name] Step 8: Received 150 tokens (input: 50, output: 100)
[AI][function_name] Step 9: Content length: 450 characters
[AI][function_name] Step 10: Cost calculated: $0.000123
[AI][function_name][Success] Request completed successfully
```
#### Error Logging Format
```
[AI][function_name][Error] OpenAI Rate Limit - waiting 60s
[AI][function_name][Error] HTTP 429 error: Rate limit exceeded (Rate limit - retry after 60s)
[AI][function_name][Error] Request timeout (60s exceeded)
[AI][function_name][Error] Failed to parse JSON response: ...
```
### 2. Image Generation with Logging
#### `generate_image()` Method
- **Purpose**: Centralized image generation with console logging
- **Features**:
- Supports OpenAI DALL-E and Runware
- Model and size validation
- Step-by-step console logging
- Error handling with detailed messages
- Cost calculation
#### Console Logging Format
```
[AI][generate_images] Step 1: Preparing image generation request...
[AI][generate_images] Provider: OpenAI
[AI][generate_images] Step 2: Using model: dall-e-3, size: 1024x1024
[AI][generate_images] Step 3: Sending request to OpenAI Images API...
[AI][generate_images] Step 4: Received response in 5.67s (status=200)
[AI][generate_images] Step 5: Image generated successfully
[AI][generate_images] Step 6: Cost: $0.0400
[AI][generate_images][Success] Image generation completed
```
### 3. Updated All Function Files
#### `functions/auto_cluster.py`
- ✅ Uses `AICore.extract_json()` for JSON parsing
- ✅ Engine calls `run_ai_request()` (via engine.py)
#### `functions/generate_ideas.py`
- ✅ Updated `generate_ideas_core()` to use `run_ai_request()`
- ✅ Console logging enabled with function name
#### `functions/generate_content.py`
- ✅ Updated `generate_content_core()` to use `run_ai_request()`
- ✅ Console logging enabled with function name
#### `functions/generate_images.py`
- ✅ Updated to use `run_ai_request()` for prompt extraction
- ✅ Updated to use `generate_image()` with logging
- ✅ Console logging enabled
### 4. Updated Engine
#### `engine.py`
- ✅ Updated to use `run_ai_request()` instead of `call_openai()`
- ✅ Passes function name for logging context
- ✅ Maintains backward compatibility
### 5. Deprecated Old Code
#### `processor.py`
- ✅ Marked as DEPRECATED
- ✅ Redirects all calls to `AICore`
- ✅ Kept for backward compatibility only
- ✅ All methods now use `AICore` internally
### 6. Edge Case Handling
#### Implemented in `run_ai_request()`:
-**API Key Validation**: Logs error if not configured
-**Prompt Length**: Logs character count
-**Rate Limits**: Detects and logs retry-after time
-**Timeouts**: Handles 60s timeout with clear error
-**JSON Parsing Errors**: Logs decode errors with context
-**Empty Responses**: Validates content exists
-**Token Overflow**: Max tokens enforced
-**Model Validation**: Auto-selects JSON mode for supported models
### 7. Standardized Request Schema
#### OpenAI Request Payload
```python
{
"model": "gpt-4o",
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.7,
"max_tokens": 4000,
"response_format": {"type": "json_object"} # Auto-enabled for supported models
}
```
#### All Functions Use Same Logic:
- Model selection (account default or override)
- JSON mode auto-enablement
- Token limits
- Temperature settings
- Error handling
### 8. Test Script Created
#### `ai/tests/test_run.py`
- ✅ Test script for all AI functions
- ✅ Tests `run_ai_request()` directly
- ✅ Tests JSON extraction
- ✅ Placeholder tests for all functions
- ✅ Can be run standalone to verify logging
## 📋 File Changes Summary
| File | Changes | Status |
|------|---------|--------|
| `ai_core.py` | Complete rewrite with `run_ai_request()` and console logging | ✅ Complete |
| `engine.py` | Updated to use `run_ai_request()` | ✅ Complete |
| `processor.py` | Marked deprecated, redirects to AICore | ✅ Complete |
| `functions/auto_cluster.py` | Uses AICore methods | ✅ Complete |
| `functions/generate_ideas.py` | Uses `run_ai_request()` | ✅ Complete |
| `functions/generate_content.py` | Uses `run_ai_request()` | ✅ Complete |
| `functions/generate_images.py` | Uses `run_ai_request()` and `generate_image()` | ✅ Complete |
| `tests/test_run.py` | Test script created | ✅ Complete |
## 🔄 Migration Path
### Old Code (Deprecated)
```python
from igny8_core.utils.ai_processor import AIProcessor
processor = AIProcessor(account=account)
result = processor._call_openai(prompt, model=model)
```
### New Code (Recommended)
```python
from igny8_core.ai.ai_core import AICore
ai_core = AICore(account=account)
result = ai_core.run_ai_request(
prompt=prompt,
model=model,
function_name='my_function'
)
```
## ✅ Verification Checklist
- [x] `run_ai_request()` created with console logging
- [x] All function files updated to use `run_ai_request()`
- [x] Engine updated to use `run_ai_request()`
- [x] Old processor code deprecated
- [x] Edge cases handled with logging
- [x] Request schema standardized
- [x] Test script created
- [x] No linting errors
- [x] Backward compatibility maintained
## 🎯 Benefits Achieved
1. **Centralized Execution**: All AI requests go through one method
2. **Consistent Logging**: Every request logs steps to console
3. **Better Debugging**: Clear step-by-step visibility
4. **Error Handling**: Comprehensive error detection and logging
5. **Reduced Duplication**: No scattered AI call logic
6. **Easy Testing**: Single point to test/mock
7. **Future Ready**: Easy to add retry logic, backoff, etc.
## 📝 Console Output Example
When running any AI function, you'll see:
```
[AI][generate_ideas] Step 1: Preparing request...
[AI][generate_ideas] Step 2: Using model: gpt-4o
[AI][generate_ideas] Step 3: Auto-enabled JSON mode for gpt-4o
[AI][generate_ideas] Step 4: Prompt length: 2345 characters
[AI][generate_ideas] Step 5: Request payload prepared (model=gpt-4o, max_tokens=4000, temp=0.7)
[AI][generate_ideas] Step 6: Sending request to OpenAI API...
[AI][generate_ideas] Step 7: Received response in 3.45s (status=200)
[AI][generate_ideas] Step 8: Received 250 tokens (input: 100, output: 150)
[AI][generate_ideas] Step 9: Content length: 600 characters
[AI][generate_ideas] Step 10: Cost calculated: $0.000250
[AI][generate_ideas][Success] Request completed successfully
```
## 🚀 Next Steps (Future Stages)
- **Stage 3**: Simplify logging (optional - console logging already implemented)
- **Stage 4**: Clean up legacy code (remove old processor completely)
- **Future**: Add retry logic, exponential backoff, request queuing

View File

@@ -1,171 +0,0 @@
# Stage 3 - Clean Logging, Unified Debug Flow & Step Traceability - COMPLETE ✅
## Summary
Successfully replaced all fragmented or frontend-based debugging systems with a consistent, lightweight backend-only logging flow. All AI activity is now tracked via structured console messages with no UI panels, no Zustand state, and no silent failures.
## ✅ Completed Deliverables
### 1. ConsoleStepTracker Created
#### `tracker.py` - ConsoleStepTracker Class
- **Purpose**: Lightweight console-based step tracker for AI functions
- **Features**:
- Logs each step to console with timestamps and clear labels
- Only logs if `DEBUG_MODE` is True
- Standardized phase methods: `init()`, `prep()`, `ai_call()`, `parse()`, `save()`, `done()`
- Error logging: `error()`, `timeout()`, `rate_limit()`, `malformed_json()`
- Retry logging: `retry()`
- Duration tracking
#### Log Format
```
[HH:MM:SS] [function_name] [PHASE] message
[HH:MM:SS] [function_name] [PHASE] ✅ success message
[HH:MM:SS] [function_name] [PHASE] [ERROR] error message
[function_name] === AI Task Complete ===
```
### 2. DEBUG_MODE Constant Added
#### `constants.py`
- Added `DEBUG_MODE = True` constant
- Controls all console logging
- Can be set to `False` in production to disable verbose logging
- All print statements check `DEBUG_MODE` before logging
### 3. Integrated Tracker into AI Functions
#### `generate_ideas.py`
- ✅ Added `ConsoleStepTracker` initialization
- ✅ Logs: INIT → PREP → AI_CALL → PARSE → SAVE → DONE
- ✅ Error handling with tracker.error()
- ✅ Passes tracker to `run_ai_request()`
#### `ai_core.py`
- ✅ Updated `run_ai_request()` to accept optional tracker parameter
- ✅ All logging now uses tracker methods
- ✅ Replaced all `print()` statements with tracker calls
- ✅ Standardized error logging format
### 4. Frontend Debug Systems Deprecated
#### `TablePageTemplate.tsx`
- ✅ Commented out `AIRequestLogsSection` component
- ✅ Commented out import of `useAIRequestLogsStore`
- ✅ Added deprecation comments
#### Frontend Store (Kept for now, but unused)
- `aiRequestLogsStore.ts` - Still exists but no longer used
- All calls to `addLog`, `updateLog`, `addRequestStep`, `addResponseStep` are deprecated
### 5. Error Standardization
#### Standardized Error Format
```
[ERROR] {function_name}: {error_type} {message}
```
#### Error Types
- `ConfigurationError` - API key not configured
- `ValidationError` - Input validation failed
- `HTTPError` - HTTP request failed
- `Timeout` - Request timeout
- `RateLimit` - Rate limit hit
- `MalformedJSON` - JSON parsing failed
- `EmptyResponse` - No content in response
- `ParseError` - Response parsing failed
- `Exception` - Unexpected exception
### 6. Example Console Output
#### Successful Execution
```
[14:23:45] [generate_ideas] [INIT] Task started
[14:23:45] [generate_ideas] [PREP] Loading account and cluster data...
[14:23:45] [generate_ideas] [PREP] Validating input...
[14:23:45] [generate_ideas] [PREP] Loading cluster with keywords...
[14:23:45] [generate_ideas] [PREP] Building prompt...
[14:23:45] [generate_ideas] [AI_CALL] Preparing request...
[14:23:45] [generate_ideas] [AI_CALL] Using model: gpt-4o
[14:23:45] [generate_ideas] [AI_CALL] Auto-enabled JSON mode for gpt-4o
[14:23:45] [generate_ideas] [AI_CALL] Prompt length: 1234 characters
[14:23:45] [generate_ideas] [AI_CALL] Request payload prepared (model=gpt-4o, max_tokens=4000, temp=0.7)
[14:23:45] [generate_ideas] [AI_CALL] Sending request to OpenAI API...
[14:23:48] [generate_ideas] [AI_CALL] Received response in 2.34s (status=200)
[14:23:48] [generate_ideas] [PARSE] Received 250 tokens (input: 100, output: 150)
[14:23:48] [generate_ideas] [PARSE] Content length: 600 characters
[14:23:48] [generate_ideas] [PARSE] Cost calculated: $0.000250
[14:23:48] [generate_ideas] [DONE] ✅ Request completed successfully (Duration: 3.12s)
[14:23:48] [generate_ideas] [PARSE] Parsing AI response...
[14:23:48] [generate_ideas] [PARSE] Parsed 1 idea(s)
[14:23:48] [generate_ideas] [SAVE] Saving idea to database...
[14:23:48] [generate_ideas] [SAVE] Saved 1 idea(s)
[14:23:48] [generate_ideas] [DONE] ✅ Idea 'My Great Idea' created successfully (Duration: 3.15s)
[generate_ideas] === AI Task Complete ===
```
#### Error Execution
```
[14:25:10] [generate_ideas] [INIT] Task started
[14:25:10] [generate_ideas] [PREP] Loading account and cluster data...
[14:25:10] [generate_ideas] [PREP] Validating input...
[14:25:10] [generate_ideas] [PREP] [ERROR] ValidationError No cluster found
```
## 📋 File Changes Summary
| File | Changes | Status |
|------|---------|--------|
| `tracker.py` | Added `ConsoleStepTracker` class | ✅ Complete |
| `constants.py` | Added `DEBUG_MODE` constant | ✅ Complete |
| `ai_core.py` | Updated to use tracker, removed print() statements | ✅ Complete |
| `generate_ideas.py` | Integrated ConsoleStepTracker | ✅ Complete |
| `TablePageTemplate.tsx` | Commented out frontend debug UI | ✅ Complete |
## 🔄 Remaining Work
### Functions Still Need Tracker Integration
- [ ] `auto_cluster.py` - Add tracker to core function
- [ ] `generate_content.py` - Add tracker to core function
- [ ] `generate_images.py` - Add tracker to core function
### Image Generation Logging
- [ ] Update `_generate_image_openai()` to use tracker
- [ ] Update `_generate_image_runware()` to use tracker
- [ ] Replace all print() statements with tracker calls
### Frontend Cleanup
- [ ] Remove or fully comment out `AIRequestLogsSection` function body
- [ ] Remove unused imports from `api.ts` and `useProgressModal.ts`
- [ ] Optionally delete `aiRequestLogsStore.ts` (or keep for reference)
## ✅ Verification Checklist
- [x] ConsoleStepTracker created with all methods
- [x] DEBUG_MODE constant added
- [x] `run_ai_request()` updated to use tracker
- [x] `generate_ideas.py` integrated with tracker
- [x] Frontend debug UI commented out
- [x] Error logging standardized
- [ ] All function files integrated (partial)
- [ ] Image generation logging updated (pending)
- [ ] All print() statements replaced (partial)
## 🎯 Benefits Achieved
1. **Unified Logging**: All AI functions use same logging format
2. **Backend-Only**: No frontend state management needed
3. **Production Ready**: Can disable logs via DEBUG_MODE
4. **Clear Traceability**: Every step visible in console
5. **Error Visibility**: All errors clearly labeled and logged
6. **No Silent Failures**: Every failure prints its cause
## 📝 Next Steps
1. Complete tracker integration in remaining functions
2. Update image generation methods
3. Remove remaining print() statements
4. Test end-to-end with all four AI flows
5. Optionally clean up frontend debug code completely

View File

@@ -1,220 +0,0 @@
# Stage 4 - Prompt Registry, Model Unification, and Final Function Hooks - COMPLETE ✅
## Summary
Successfully created a centralized prompt registry system, unified model configurations, and standardized all AI function execution with clean, minimal function files.
## ✅ Completed Deliverables
### 1. Prompt Registry System Created
#### `ai/prompts.py` - PromptRegistry Class
- **Purpose**: Centralized prompt management with hierarchical resolution
- **Features**:
- Hierarchical prompt resolution:
1. Task-level `prompt_override` (if exists)
2. DB prompt for (account, function)
3. Default fallback from registry
- Supports both `.format()` style and `[IGNY8_*]` placeholder replacement
- Function-to-prompt-type mapping
- Convenience methods: `get_image_prompt_template()`, `get_negative_prompt()`
#### Prompt Resolution Priority
```python
# Priority 1: Task override
if task.prompt_override:
use task.prompt_override
# Priority 2: DB prompt
elif DB prompt for (account, function) exists:
use DB prompt
# Priority 3: Default fallback
else:
use default from registry
```
### 2. Model Configuration Centralized
#### `ai/settings.py` - MODEL_CONFIG
- **Purpose**: Centralized model configurations for all AI functions
- **Configurations**:
```python
MODEL_CONFIG = {
"auto_cluster": {
"model": "gpt-4o-mini",
"max_tokens": 3000,
"temperature": 0.7,
"response_format": {"type": "json_object"},
},
"generate_ideas": {
"model": "gpt-4.1",
"max_tokens": 4000,
"temperature": 0.7,
"response_format": {"type": "json_object"},
},
"generate_content": {
"model": "gpt-4.1",
"max_tokens": 8000,
"temperature": 0.7,
"response_format": None, # Text output
},
"generate_images": {
"model": "dall-e-3",
"size": "1024x1024",
"provider": "openai",
},
}
```
#### Helper Functions
- `get_model_config(function_name)` - Get full config
- `get_model(function_name)` - Get model name
- `get_max_tokens(function_name)` - Get max tokens
- `get_temperature(function_name)` - Get temperature
### 3. Updated All AI Functions
#### `functions/auto_cluster.py`
- ✅ Uses `PromptRegistry.get_prompt()`
- ✅ Uses `get_model_config()` for model settings
- ✅ Removed direct `get_prompt_value()` calls
#### `functions/generate_ideas.py`
- ✅ Uses `PromptRegistry.get_prompt()` with context
- ✅ Uses `get_model_config()` for model settings
- ✅ Clean prompt building with context variables
#### `functions/generate_content.py`
- ✅ Uses `PromptRegistry.get_prompt()` with task support
- ✅ Uses `get_model_config()` for model settings
- ✅ Supports task-level prompt overrides
#### `functions/generate_images.py`
- ✅ Uses `PromptRegistry.get_prompt()` for extraction
- ✅ Uses `PromptRegistry.get_image_prompt_template()`
- ✅ Uses `PromptRegistry.get_negative_prompt()`
- ✅ Uses `get_model_config()` for model settings
### 4. Updated Engine
#### `engine.py`
- ✅ Uses `get_model_config()` instead of `fn.get_model()`
- ✅ Passes model config to `run_ai_request()`
- ✅ Unified model selection across all functions
### 5. Standardized Response Format
All functions now return consistent format:
```python
{
"success": True/False,
"output": "HTML or image_url or data",
"raw": raw_response_json, # Optional
"meta": {
"word_count": 1536, # For content
"keywords": [...], # For clusters
"model_used": "gpt-4.1",
"tokens": 250,
"cost": 0.000123
},
"error": None or error_message
}
```
## 📋 File Changes Summary
| File | Changes | Status |
|------|---------|--------|
| `prompts.py` | Created PromptRegistry class | ✅ Complete |
| `settings.py` | Created MODEL_CONFIG and helpers | ✅ Complete |
| `functions/auto_cluster.py` | Updated to use registry and settings | ✅ Complete |
| `functions/generate_ideas.py` | Updated to use registry and settings | ✅ Complete |
| `functions/generate_content.py` | Updated to use registry and settings | ✅ Complete |
| `functions/generate_images.py` | Updated to use registry and settings | ✅ Complete |
| `engine.py` | Updated to use model config | ✅ Complete |
| `__init__.py` | Exported new modules | ✅ Complete |
## 🔄 Migration Path
### Old Code (Deprecated)
```python
from igny8_core.modules.system.utils import get_prompt_value, get_default_prompt
prompt_template = get_prompt_value(account, 'clustering')
prompt = prompt_template.replace('[IGNY8_KEYWORDS]', keywords_text)
```
### New Code (Recommended)
```python
from igny8_core.ai.prompts import PromptRegistry
from igny8_core.ai.settings import get_model_config
# Get prompt from registry
prompt = PromptRegistry.get_prompt(
function_name='auto_cluster',
account=account,
context={'KEYWORDS': keywords_text}
)
# Get model config
model_config = get_model_config('auto_cluster')
```
## ✅ Verification Checklist
- [x] PromptRegistry created with hierarchical resolution
- [x] MODEL_CONFIG created with all function configs
- [x] All functions updated to use registry
- [x] All functions updated to use model config
- [x] Engine updated to use model config
- [x] Response format standardized
- [x] No direct prompt utility calls in functions
- [x] Task-level overrides supported
- [x] DB prompts supported
- [x] Default fallbacks working
## 🎯 Benefits Achieved
1. **Centralized Prompts**: All prompts in one registry
2. **Hierarchical Resolution**: Task → DB → Default
3. **Model Unification**: All models configured in one place
4. **Easy Customization**: Tenant admins can override prompts
5. **Consistent Execution**: All functions use same pattern
6. **Traceability**: Prompt source clearly identifiable
7. **Minimal Functions**: Functions are clean and focused
## 📝 Prompt Source Traceability
Each prompt execution logs its source:
- `[PROMPT] Using task-level prompt override for generate_content`
- `[PROMPT] Using DB prompt for generate_ideas (account 123)`
- `[PROMPT] Using default prompt for auto_cluster`
## 🚀 Final Structure
```
/ai/
├── functions/
│ ├── auto_cluster.py ← Uses registry + settings
│ ├── generate_ideas.py ← Uses registry + settings
│ ├── generate_content.py ← Uses registry + settings
│ └── generate_images.py ← Uses registry + settings
├── prompts.py ← Prompt Registry ✅
├── settings.py ← Model Configs ✅
├── ai_core.py ← Unified execution ✅
├── engine.py ← Uses settings ✅
└── tracker.py ← Console logging ✅
```
## ✅ Expected Outcomes Achieved
- ✅ All AI executions use common format
- ✅ Prompt customization is dynamic and override-able
- ✅ No duplication across AI functions
- ✅ Every AI task has:
- ✅ Clean inputs
- ✅ Unified execution
- ✅ Standard outputs
- ✅ Clear error tracking
- ✅ Prompt traceability

View File

@@ -1,749 +0,0 @@
# IGNY8 System Architecture
**Version:** 1.0
**Last Updated:** 2025-01-XX
**Purpose:** Complete system architecture documentation covering design patterns, principles, tech stack, and structural organization.
---
## Table of Contents
1. [System Overview](#system-overview)
2. [Tech Stack](#tech-stack)
3. [Core Architecture Principles](#core-architecture-principles)
4. [Project Structure](#project-structure)
5. [Key Architectural Patterns](#key-architectural-patterns)
6. [Multi-Tenancy Architecture](#multi-tenancy-architecture)
7. [Module Organization](#module-organization)
8. [API Architecture](#api-architecture)
9. [Frontend Architecture](#frontend-architecture)
10. [Backend Architecture](#backend-architecture)
11. [Database Architecture](#database-architecture)
12. [Security Architecture](#security-architecture)
13. [Deployment Architecture](#deployment-architecture)
---
## System Overview
**IGNY8** is a full-stack SaaS platform for SEO keyword management and AI-driven content generation. The system migrated from a WordPress plugin architecture to a modern Django + React architecture, providing a scalable, multi-account platform for content planning and generation.
### Core Capabilities
- **Multi-Account SaaS Platform**: Complete account isolation with site/sector hierarchy
- **SEO Keyword Management**: Import, organize, and cluster keywords
- **AI-Powered Content Planning**: Automated keyword clustering and content idea generation
- **AI Content Generation**: Automated blog post and article generation
- **Image Generation**: AI-powered image generation for content
- **WordPress Integration**: Publish content directly to WordPress sites
- **Subscription Management**: Plan-based limits and billing integration
---
## Tech Stack
### Backend
- **Framework**: Django 5.2+ with Django REST Framework (DRF)
- **Database**: PostgreSQL
- **Task Queue**: Celery with Redis broker
- **Authentication**: JWT (JSON Web Tokens) + Session-based auth
- **API**: RESTful API with DRF ViewSets
- **Caching**: Redis
- **File Storage**: Local filesystem (configurable for S3)
### Frontend
- **Framework**: React 19 with TypeScript
- **Build Tool**: Vite
- **Styling**: Tailwind CSS
- **State Management**: Zustand
- **Routing**: React Router v6
- **HTTP Client**: Fetch API with custom wrapper
- **UI Components**: Custom component library
### Infrastructure
- **Containerization**: Docker & Docker Compose
- **Reverse Proxy**: Caddy (HTTPS on port 443)
- **Process Management**: Supervisor (for Celery workers)
- **Monitoring**: Portainer (optional)
### Development Tools
- **Backend**: Python 3.11+, pip, virtualenv
- **Frontend**: Node.js 18+, npm/yarn
- **Version Control**: Git
- **Code Quality**: ESLint, Prettier (frontend), Black, Flake8 (backend)
---
## Core Architecture Principles
### 1. Configuration-Driven Everything
**Principle**: Zero HTML/JSX duplication - All UI rendered from configuration.
- **Tables, filters, forms** all driven by config files
- **Single source of truth** - Change config, UI updates everywhere
- **Page-local config** for page-specific settings
- **Shared snippets** for reusable column/filter/action definitions
**Implementation**:
- Frontend: Config files in `/config/pages/` and `/config/snippets/`
- Backend: DRF serializers and ViewSet actions
### 2. Multi-Tenancy Foundation
**Principle**: Complete account isolation with automatic filtering.
- All models inherit `AccountBaseModel` with automatic account isolation
- All ViewSets inherit `AccountModelViewSet` with automatic account filtering
- Middleware injects account context from JWT on every request
- Site > Sector hierarchy for content organization
**Implementation**:
- Base models: `AccountBaseModel`, `SiteSectorBaseModel`
- Base ViewSets: `AccountModelViewSet`, `SiteSectorModelViewSet`
- Middleware: `AccountContextMiddleware` sets `request.account`
### 3. Template System (4 Universal Templates)
**Principle**: Reusable templates for all page types.
- **DashboardTemplate**: Module home pages (KPIs, workflow steps, charts)
- **TablePageTemplate**: CRUD table pages (Keywords, Clusters, Tasks, etc.)
- **FormPageTemplate**: Settings/form pages (Settings, Integration, etc.)
- **SystemPageTemplate**: System/admin pages (Logs, Status, Monitoring)
**Implementation**:
- Frontend: `/templates/` directory with 4 template components
- Config-driven: Templates accept config objects for customization
### 4. Unified AI Processor
**Principle**: Single interface for all AI operations.
- Single `AIProcessor` class handles all AI operations
- Manual and automated workflows use same functions
- Action-based routing: 'clustering', 'ideas', 'content_generation', 'image_generation'
- Account-specific API keys and model configuration
**Implementation**:
- Backend: `AIProcessor` class in `/utils/ai_processor.py`
- Integration: Loads API keys from `IntegrationSettings` model
### 5. Module-Based Organization
**Principle**: Clear module boundaries with shared utilities.
- Each module = Django app (`/modules/{module}/`)
- Clear module boundaries with shared utilities
- Module router pattern for subpage routing
- Consistent structure across modules
**Implementation**:
- Backend: `/modules/planner/`, `/modules/writer/`, `/modules/system/`, `/modules/billing/`
- Frontend: `/pages/Planner/`, `/pages/Writer/`, `/pages/Settings/`, etc.
---
## Project Structure
```
igny8/
├── backend/ # Django backend
│ └── igny8_core/ # Django project
│ ├── auth/ # Multi-tenancy, User, Account, Plan models
│ │ ├── models.py # Account, User, Plan, Site, Sector, Industry models
│ │ ├── views.py # Account, User, Site, Sector ViewSets
│ │ ├── serializers.py # Account, User, Plan serializers
│ │ └── urls.py # Auth module URLs
│ ├── modules/ # Feature modules
│ │ ├── planner/ # Keywords, Clusters, Ideas
│ │ │ ├── models.py # Keywords, Clusters, ContentIdeas models
│ │ │ ├── views.py # KeywordViewSet, ClusterViewSet, ContentIdeasViewSet
│ │ │ ├── tasks.py # Celery tasks for AI operations
│ │ │ ├── serializers.py # Model serializers
│ │ │ └── urls.py # Planner module URLs
│ │ ├── writer/ # Tasks, Content, Images
│ │ │ ├── models.py # Tasks, Content, Images models
│ │ │ ├── views.py # TasksViewSet
│ │ │ ├── tasks.py # Celery tasks for content/image generation
│ │ │ └── urls.py # Writer module URLs
│ │ ├── system/ # Settings, Prompts, Integration
│ │ │ ├── models.py # AIPrompt, IntegrationSettings, AuthorProfile, Strategy
│ │ │ ├── views.py # AIPromptViewSet, AuthorProfileViewSet
│ │ │ ├── integration_views.py # IntegrationSettingsViewSet, task_progress
│ │ │ ├── utils.py # Default prompts, prompt loading
│ │ │ └── urls.py # System module URLs
│ │ └── billing/ # Credits, Transactions, Usage
│ │ ├── models.py # CreditTransaction, UsageLog models
│ │ ├── views.py # Billing ViewSets
│ │ └── services.py # CreditService
│ ├── api/ # API base classes
│ │ └── base.py # AccountModelViewSet, SiteSectorModelViewSet
│ ├── utils/ # Shared utilities
│ │ ├── ai_processor.py # Unified AI interface
│ │ └── content_normalizer.py # Content processing utilities
│ ├── middleware/ # Custom middleware
│ │ ├── account.py # AccountContextMiddleware (sets request.account)
│ │ └── resource_tracker.py # ResourceTrackerMiddleware (API metrics)
│ ├── settings.py # Django settings
│ ├── urls.py # Root URL configuration
│ └── celery.py # Celery configuration
├── frontend/ # React frontend
│ └── src/
│ ├── pages/ # Page components
│ │ ├── Planner/ # KeywordsPage, ClustersPage, IdeasPage, Dashboard
│ │ ├── Writer/ # TasksPage, DraftsPage, PublishedPage, Dashboard
│ │ ├── Settings/ # General, Integration, Status, ImportExport
│ │ ├── Billing/ # Credits, Transactions, Usage
│ │ └── AuthPages/ # SignIn, SignUp
│ ├── templates/ # 4 master templates
│ │ ├── DashboardTemplate.tsx
│ │ ├── TablePageTemplate.tsx
│ │ ├── FormPageTemplate.tsx
│ │ └── SystemPageTemplate.tsx
│ ├── components/ # UI components
│ │ ├── layout/ # AppLayout, Sidebar, Header, Breadcrumbs
│ │ ├── table/ # DataTable, Filters, Actions, Pagination
│ │ ├── ui/ # Button, Card, Modal, Toast, etc.
│ │ └── auth/ # ProtectedRoute, Auth components
│ ├── config/ # Configuration files
│ │ ├── pages/ # Page-specific configs
│ │ │ └── keywords.config.tsx
│ │ ├── snippets/ # Shared column/filter/action definitions
│ │ │ ├── columns.snippets.ts
│ │ │ ├── filters.snippets.ts
│ │ │ └── actions.snippets.ts
│ │ └── routes.config.ts # Route configuration
│ ├── store/ # Zustand stores
│ │ ├── authStore.ts # Authentication state
│ │ ├── plannerStore.ts # Planner module state
│ │ ├── siteStore.ts # Site selection state
│ │ └── aiRequestLogsStore.ts # AI request/response logs
│ ├── services/ # API clients
│ │ └── api.ts # fetchAPI, API functions
│ ├── hooks/ # Custom React hooks
│ │ ├── useProgressModal.ts # Progress modal for long-running tasks
│ │ └── useAuth.ts # Authentication hook
│ ├── layout/ # Layout components
│ │ └── AppLayout.tsx # Main app layout wrapper
│ ├── App.tsx # Root component with routing
│ └── main.tsx # Entry point
└── docs/ # Documentation
└── ActiveDocs/ # Active documentation
├── 01-ARCHITECTURE.md # This file
├── 02-FRONTEND.md # Frontend documentation
├── 03-BACKEND.md # Backend documentation
├── 04-AI-FUNCTIONS.md # AI functions documentation
└── 05-ACCOUNT-USER-PLAN.md # Account/User/Plan documentation
```
---
## Key Architectural Patterns
### Configuration System (Page-Local Config + Shared Snippets)
**Rule**: Config = Page-Local, Snippets = Shared
**Structure**:
```
/pages/Planner/KeywordsPage.tsx
├── Imports snippets from /config/snippets/
├── Defines page-local tableConfig, filterConfig, actionsConfig
└── Passes config to TablePageTemplate
/config/snippets/
├── columns.snippets.ts # statusColumn, titleColumn, etc.
├── filters.snippets.ts # statusFilter, dateRangeFilter, etc.
├── actions.snippets.ts # commonActions, bulkActions, etc.
```
**Benefits**:
- Reusable components across pages
- Page-specific customization
- Single source of truth for shared definitions
### Base ViewSet Pattern
**Backend Pattern**: All ViewSets inherit from base classes for consistent behavior.
**Base Classes**:
- `AccountModelViewSet`: Automatic account filtering
- `SiteSectorModelViewSet`: Account + site/sector filtering + access control
**Benefits**:
- Consistent access control
- Automatic account isolation
- Reduced code duplication
### Celery Task Pattern
**Pattern**: Long-running operations use Celery tasks with progress tracking.
**Structure**:
1. API endpoint queues Celery task
2. Task updates progress via `update_state()`
3. Frontend polls `task_progress` endpoint
4. Progress displayed in modal
**Benefits**:
- Non-blocking API responses
- Real-time progress updates
- Scalable background processing
### AI Function Pattern
**Pattern**: All AI functions follow consistent structure.
**Structure**:
1. API Endpoint: Validates input, queues Celery task
2. Celery Task: Wraps core function with progress tracking
3. Core Function: Business logic, calls AIProcessor
4. AIProcessor: Makes API calls, returns structured data
5. Core Function: Saves results to database
**Benefits**:
- Consistent error handling
- Progress tracking
- Reusable AI interface
---
## Multi-Tenancy Architecture
### Account Isolation
**Principle**: All data is isolated by account.
**Implementation**:
- All models inherit `AccountBaseModel` (has `account` ForeignKey)
- All ViewSets inherit `AccountModelViewSet` (filters by `request.account`)
- Middleware sets `request.account` from JWT token
**Access Control**:
- Admin/Developer users: Bypass account filtering (see all accounts)
- System account users: Bypass account filtering (see all accounts)
- Regular users: Only see data from their account
### Site/Sector Hierarchy
**Structure**:
```
Account (1) ──< (N) Site
Site (1) ──< (1-5) Sector
Sector (1) ──< (N) Keywords, Clusters, ContentIdeas, Tasks
```
**Implementation**:
- Models inherit `SiteSectorBaseModel` (has `site` and `sector` ForeignKeys)
- ViewSets inherit `SiteSectorModelViewSet` (filters by accessible sites)
- User access control via `User.get_accessible_sites()`
**Site Access Control**:
- System account users: All active sites
- Developers: All active sites
- Owners/Admins: All sites in their account
- Editors/Viewers: Only sites granted via `SiteUserAccess`
---
## Module Organization
### Planner Module
**Purpose**: Keyword management and content planning.
**Models**:
- `Keywords`: Individual keywords with volume, difficulty, intent
- `Clusters`: Keyword clusters (groups of related keywords)
- `ContentIdeas`: Content ideas generated from clusters
**ViewSets**:
- `KeywordViewSet`: CRUD + `auto_cluster` action
- `ClusterViewSet`: CRUD + `auto_generate_ideas` action
- `ContentIdeasViewSet`: CRUD operations
**Tasks**:
- `auto_cluster_keywords_task`: AI-powered keyword clustering
- `auto_generate_ideas_task`: AI-powered content idea generation
### Writer Module
**Purpose**: Content generation and management.
**Models**:
- `Tasks`: Content generation tasks
- `Content`: Generated content (HTML)
- `Images`: Generated images for tasks
**ViewSets**:
- `TasksViewSet`: CRUD + `auto_generate_content`, `auto_generate_images` actions
**Tasks**:
- `auto_generate_content_task`: AI-powered content generation
- `auto_generate_images_task`: AI-powered image generation
### System Module
**Purpose**: System settings, prompts, and integrations.
**Models**:
- `AIPrompt`: AI prompt templates (clustering, ideas, content, images)
- `IntegrationSettings`: API keys and configuration (OpenAI, Runware, etc.)
- `AuthorProfile`: Writing style profiles
- `Strategy`: Content strategies per sector
**ViewSets**:
- `AIPromptViewSet`: CRUD for prompts
- `IntegrationSettingsViewSet`: CRUD + `test_openai`, `test_runware`, `generate_image`, `task_progress` actions
- `AuthorProfileViewSet`: CRUD for author profiles
- `StrategyViewSet`: CRUD for strategies
### Billing Module
**Purpose**: Credits, transactions, and usage tracking.
**Models**:
- `CreditTransaction`: Credit purchase/usage transactions
- `UsageLog`: Daily/monthly usage tracking
**ViewSets**:
- `CreditTransactionViewSet`: CRUD for transactions
- `UsageLogViewSet`: Read-only usage logs
**Services**:
- `CreditService`: Credit calculation and deduction logic
---
## API Architecture
### RESTful API Design
**Base URL**: `/api/v1/`
**Endpoint Structure**:
- `/api/v1/planner/keywords/` - Keywords CRUD
- `/api/v1/planner/keywords/auto_cluster/` - Auto-cluster action
- `/api/v1/planner/clusters/` - Clusters CRUD
- `/api/v1/planner/clusters/auto_generate_ideas/` - Auto-generate ideas action
- `/api/v1/writer/tasks/` - Tasks CRUD
- `/api/v1/writer/tasks/auto_generate_content/` - Auto-generate content action
- `/api/v1/system/settings/task_progress/{task_id}/` - Task progress polling
### Authentication
**Methods**:
- JWT (JSON Web Tokens) - Primary method
- Session-based auth - Fallback for admin
**Flow**:
1. User signs in → Backend returns JWT token
2. Frontend stores token in localStorage
3. Frontend includes token in `Authorization: Bearer {token}` header
4. Backend middleware validates token and sets `request.user` and `request.account`
### Response Format
**Success Response**:
```json
{
"success": true,
"data": { ... },
"message": "Optional message"
}
```
**Error Response**:
```json
{
"success": false,
"message": "Error message",
"errors": { ... }
}
```
### Pagination
**Format**: Page-based pagination
**Response**:
```json
{
"count": 100,
"next": "http://api.example.com/api/v1/resource/?page=2",
"previous": null,
"results": [ ... ]
}
```
---
## Frontend Architecture
### Component Hierarchy
```
App
└── AppLayout
├── Sidebar (navigation)
├── Header (user menu, notifications)
└── Main Content
└── Page Component
└── Template (DashboardTemplate, TablePageTemplate, etc.)
└── Components (DataTable, Filters, etc.)
```
### State Management
**Zustand Stores**:
- `authStore`: Authentication state (user, token, account)
- `plannerStore`: Planner module state
- `siteStore`: Selected site/sector
- `aiRequestLogsStore`: AI request/response logs
- `pageSizeStore`: Table page size preference
**Local State**: React `useState` for component-specific state
### Routing
**Structure**: React Router v6 with nested routes
**Routes**:
- `/` - Home/Dashboard
- `/planner` - Planner Dashboard
- `/planner/keywords` - Keywords page
- `/planner/clusters` - Clusters page
- `/planner/ideas` - Ideas page
- `/writer` - Writer Dashboard
- `/writer/tasks` - Tasks page
- `/settings` - Settings pages
**Protected Routes**: All routes except `/signin` and `/signup` require authentication
---
## Backend Architecture
### Model Inheritance Hierarchy
```
models.Model
└── AccountBaseModel (adds account ForeignKey)
└── SiteSectorBaseModel (adds site, sector ForeignKeys)
└── Keywords, Clusters, ContentIdeas, Tasks, etc.
```
### ViewSet Inheritance Hierarchy
```
viewsets.ModelViewSet
└── AccountModelViewSet (adds account filtering)
└── SiteSectorModelViewSet (adds site/sector filtering)
└── KeywordViewSet, ClusterViewSet, TasksViewSet, etc.
```
### Middleware Stack
1. **SecurityMiddleware**: Django security middleware
2. **SessionMiddleware**: Session management
3. **AuthenticationMiddleware**: User authentication
4. **AccountContextMiddleware**: Sets `request.account` from JWT
5. **ResourceTrackerMiddleware**: Tracks API request metrics
### Celery Task Architecture
**Broker**: Redis
**Workers**: Separate Celery worker processes
**Task Structure**:
```python
@shared_task(bind=True)
def my_task(self, ...):
# Update progress
self.update_state(state='PROGRESS', meta={...})
# Do work
result = do_work()
# Return result
return result
```
**Progress Tracking**:
- Frontend polls `/api/v1/system/settings/task_progress/{task_id}/`
- Backend returns task state and meta information
- Progress displayed in modal
---
## Database Architecture
### Core Tables
- `igny8_accounts`: Account information
- `igny8_users`: User accounts
- `igny8_plans`: Subscription plans
- `igny8_subscriptions`: Active subscriptions
- `igny8_sites`: Sites within accounts
- `igny8_sectors`: Sectors within sites
- `igny8_industries`: Global industry templates
- `igny8_industry_sectors`: Industry sector templates
### Planner Tables
- `igny8_keywords`: Keywords
- `igny8_clusters`: Keyword clusters
- `igny8_content_ideas`: Content ideas
### Writer Tables
- `igny8_tasks`: Content generation tasks
- `igny8_content`: Generated content
- `igny8_images`: Generated images
### System Tables
- `igny8_ai_prompts`: AI prompt templates
- `igny8_integration_settings`: API keys and configuration
- `igny8_author_profiles`: Writing style profiles
- `igny8_strategies`: Content strategies
### Billing Tables
- `igny8_credit_transactions`: Credit transactions
- `igny8_usage_logs`: Usage tracking
### Indexes
**Account Isolation**: All tables have indexes on `account`
**Site/Sector Filtering**: Tables with site/sector have composite indexes on `(account, site, sector)`
**Performance**: Indexes on frequently queried fields (status, created_at, etc.)
---
## Security Architecture
### Authentication
**JWT Tokens**:
- Signed with secret key
- Contains user ID and account ID
- Expires after configured time
- Stored in localStorage (frontend)
**Session Auth**:
- Fallback for admin interface
- Django session framework
### Authorization
**Role-Based Access Control (RBAC)**:
- `developer`: Full system access
- `owner`: Full account access
- `admin`: Account admin access
- `editor`: Content editing access
- `viewer`: Read-only access
**Access Control**:
- Account-level: Automatic filtering by `request.account`
- Site-level: Filtering by `user.get_accessible_sites()`
- Action-level: Permission checks in ViewSet actions
### Data Isolation
**Account Isolation**:
- All queries filtered by account
- Admin/Developer override for system accounts
**Site Access Control**:
- Users can only access granted sites
- Admin/Developer override for all sites
### API Security
**CORS**: Configured for frontend domain
**CSRF**: Enabled for session-based auth
**Rate Limiting**: (Future implementation)
**Input Validation**: DRF serializers validate all input
---
## Deployment Architecture
### Docker Compose Setup
**Services**:
- `backend`: Django application (port 8010/8011)
- `frontend`: React application (port 5173/8021)
- `db`: PostgreSQL database
- `redis`: Redis for Celery broker and caching
- `caddy`: Reverse proxy (HTTPS on port 443)
- `celery-worker`: Celery worker process
- `celery-beat`: Celery beat scheduler (optional)
### Environment Configuration
**Backend**:
- `DJANGO_SETTINGS_MODULE`: Django settings module
- `DATABASE_URL`: PostgreSQL connection string
- `REDIS_URL`: Redis connection string
- `SECRET_KEY`: Django secret key
- `OPENAI_API_KEY`: OpenAI API key (fallback)
**Frontend**:
- `VITE_API_URL`: Backend API URL
- `VITE_APP_NAME`: Application name
### Scaling Considerations
**Horizontal Scaling**:
- Multiple Celery workers
- Multiple backend instances (load balanced)
- Multiple frontend instances (static files)
**Vertical Scaling**:
- Database connection pooling
- Redis connection pooling
- Celery worker concurrency
### Monitoring
**Application Monitoring**:
- ResourceTrackerMiddleware tracks API request metrics
- Celery task monitoring via Flower (optional)
**Infrastructure Monitoring**:
- Portainer for container monitoring
- Database monitoring via PostgreSQL logs
- Redis monitoring via Redis CLI
---
## Summary
The IGNY8 architecture is built on:
1. **Configuration-Driven Design**: Zero duplication, single source of truth
2. **Multi-Tenancy Foundation**: Complete account isolation with site/sector hierarchy
3. **Template System**: 4 universal templates for all page types
4. **Unified AI Interface**: Single AIProcessor for all AI operations
5. **Module-Based Organization**: Clear boundaries with shared utilities
6. **RESTful API**: Consistent API design with DRF
7. **Modern Frontend**: React + TypeScript with Zustand state management
8. **Scalable Backend**: Django + Celery for async processing
9. **Security First**: JWT auth, RBAC, data isolation
10. **Docker Deployment**: Containerized for easy deployment and scaling
This architecture ensures scalability, maintainability, and extensibility while maintaining a clean separation of concerns across modules.

View File

@@ -1,225 +0,0 @@
# Git Auto-Sync Architecture - Complete Picture
## Overview
This document explains how the automatic git synchronization works between Gitea (bare repository) and the VPS working copy.
## Architecture Components
### 1. **Bare Repository (Gitea Server)**
- **Host Path**: `/data/app/gitea/git/repositories/salman/igny8.git`
- **Container Path**: `/data/git/repositories/salman/igny8.git`
- **Type**: Bare repository (no working tree)
- **Purpose**: Stores all commits, branches, and git history
- **Access**: Served by Gitea at `https://git.igny8.com/salman/igny8.git`
### 2. **Working Copy (VPS Deployment)**
- **Host Path**: `/data/app/igny8`
- **Container Path**: `/deploy/igny8` (mounted from host)
- **Type**: Full git repository with working tree
- **Purpose**: Live application code that runs on VPS
- **Remotes**:
- `origin`: `https://git.igny8.com/salman/igny8.git` (HTTPS remote)
- `deploy`: `/data/git/repositories/salman/igny8.git` (local bare repo path)
### 3. **Docker Volume Mount**
```yaml
# From docker-compose.yml
volumes:
- ./gitea:/data # Gitea data directory
- /data/app/igny8:/deploy/igny8:rw # Mount working copy into container
```
This mount makes the VPS working copy accessible inside the Gitea container at `/deploy/igny8`.
### 4. **Post-Receive Hook**
- **Location**: `/data/app/gitea/git/repositories/salman/igny8.git/hooks/post-receive`
- **Trigger**: Automatically runs after every `git push` to Gitea
- **Execution Context**: Runs inside Gitea container, in the bare repository directory
## Complete Flow: Push to Auto-Update
### Step 1: Developer Pushes to Gitea
```bash
git push origin main
# or
git push https://git.igny8.com/salman/igny8.git main
```
### Step 2: Gitea Receives Push
- Push arrives at Gitea server
- Gitea validates and stores commits in bare repository
- Bare repo is updated: `/data/app/gitea/git/repositories/salman/igny8.git`
### Step 3: Post-Receive Hook Executes
The hook runs automatically with this context:
- **Current Directory**: `/data/git/repositories/salman/igny8.git` (bare repo)
- **Environment**: Inside Gitea container
- **Access**: Can access both bare repo and mounted working copy
### Step 4: Hook Logic Breakdown
```bash
# 1. Define paths
DEPLOY_DIR="/deploy/igny8" # Working copy (mounted from host)
SOURCE_REPO="$(pwd)" # Bare repo: /data/git/repositories/salman/igny8.git
# 2. Check if working copy is a git repository
if [ -d "$DEPLOY_DIR/.git" ]; then
# Working copy exists and is a full git repo
# 3. Set up git command with explicit paths
GIT_CMD="git --git-dir=$DEPLOY_DIR/.git --work-tree=$DEPLOY_DIR"
# 4. Add 'deploy' remote if it doesn't exist
# This points to the bare repo (no network needed, direct file access)
if ! $GIT_CMD remote get-url deploy >/dev/null 2>&1; then
$GIT_CMD remote add deploy "$SOURCE_REPO"
fi
# 5. Fetch latest commits from bare repo
# Fetches directly from file system, no HTTPS/SSH needed
$GIT_CMD fetch deploy main
# 6. Reset working tree to latest commit
# This updates all files in /data/app/igny8 to match latest commit
$GIT_CMD reset --hard remotes/deploy/main
# 7. Update origin/main tracking branch
# This is CRITICAL: tells git that local HEAD matches origin/main
# Without this, git status shows "ahead" even though files are synced
$GIT_CMD update-ref refs/remotes/origin/main HEAD
else
# First time setup: working copy doesn't exist yet
# Checkout files from bare repo to create working copy
export GIT_DIR="$SOURCE_REPO"
export GIT_WORK_TREE="$DEPLOY_DIR"
git checkout -f main
fi
```
### Step 5: Result
- **Files Updated**: All files in `/data/app/igny8` now match latest commit
- **Git Status**: Shows "up to date with origin/main" (no phantom commits)
- **Application**: If using volume mounts, containers see changes immediately
## Why This Design?
### Problem We Solved
1. **Phantom Commits**: Previously, hook updated files but not git metadata, causing git to think local was "ahead"
2. **Manual Sync Required**: Had to manually `git pull` after every push
3. **Sync Issues**: Working tree and git metadata were out of sync
### Solution
1. **Direct File System Access**: Hook uses `deploy` remote pointing to bare repo path (no network overhead)
2. **Complete Sync**: Updates both files AND git metadata (tracking branches)
3. **Automatic**: Runs on every push, no manual intervention needed
## Key Git Concepts Used
### 1. Bare Repository
- Repository without working tree
- Only contains `.git` contents (objects, refs, etc.)
- Used by servers to store code without checking out files
### 2. Working Tree
- Directory with actual files you can edit
- Has `.git` directory with repository metadata
- This is what developers work with
### 3. Remote Tracking Branches
- `refs/remotes/origin/main`: Git's record of what `origin/main` was last time we fetched
- When we update this to match HEAD, git knows we're in sync
- Without updating this, git compares HEAD to stale tracking branch → shows "ahead"
### 4. Git Reset --hard
- Moves HEAD to specified commit
- Updates working tree to match that commit
- Discards any local changes (force update)
## File System Layout
```
Host System:
├── /data/app/gitea/
│ └── git/repositories/salman/igny8.git/ (bare repo)
│ ├── objects/ (all commits, trees, blobs)
│ ├── refs/ (branches, tags)
│ └── hooks/
│ └── post-receive (auto-sync hook)
└── /data/app/igny8/ (working copy)
├── .git/ (git metadata)
│ ├── config (has 'origin' and 'deploy' remotes)
│ └── refs/
│ └── remotes/
│ ├── origin/main (tracking branch)
│ └── deploy/main (tracking branch)
├── backend/ (actual application files)
├── frontend/
└── ...
Inside Gitea Container:
├── /data/git/repositories/salman/igny8.git/ (same as host bare repo)
└── /deploy/igny8/ (mounted from /data/app/igny8)
└── (same contents as host /data/app/igny8)
```
## Checking Status
### On Host VPS
```bash
cd /data/app/igny8
git status # Should show "up to date with origin/main"
git log --oneline -5 # See recent commits
git remote -v # See remotes (origin + deploy)
```
### Hook Logs
```bash
docker exec gitea cat /data/gitea/log/hooks.log
```
### Manual Sync (if needed)
```bash
cd /data/app/igny8
git pull origin main # Pull from HTTPS remote
# OR
git fetch deploy main # Fetch from local bare repo
git reset --hard remotes/deploy/main
```
## Troubleshooting
### Issue: "Your branch is ahead of origin/main"
**Cause**: Hook didn't update `refs/remotes/origin/main` tracking branch
**Fix**: Hook should run `git update-ref refs/remotes/origin/main HEAD` (already in hook)
### Issue: Files not updating after push
**Check**:
1. Hook logs: `docker exec gitea cat /data/gitea/log/hooks.log`
2. Hook executable: `ls -la /data/app/gitea/git/repositories/salman/igny8.git/hooks/post-receive`
3. Mount exists: `docker exec gitea ls -la /deploy/igny8`
### Issue: Hook not running
**Check**:
1. Gitea logs: `docker logs gitea --tail 50`
2. Hook file exists and is executable
3. Push actually succeeded (check Gitea web UI)
## Summary
**The Complete Flow:**
1. Developer pushes → Gitea bare repo updated
2. Post-receive hook triggers automatically
3. Hook fetches from bare repo (via `deploy` remote)
4. Hook resets working copy to latest commit
5. Hook updates tracking branch metadata
6. VPS working copy is now in sync
7. Application containers see updated files (via volume mounts)
**Key Innovation:**
- Uses local file system path (`deploy` remote) instead of HTTPS
- Updates both files AND git metadata
- Fully automatic, no manual steps required

View File

@@ -0,0 +1,59 @@
## [0.1] - 2025-01-15
### Initial Release - Complete Refactor
- **Phase 1 Complete**: Global Role & Scope Index implemented
- **Phase 2 Complete**: Folder restructure & component isolation
- **Phase 2.5 Complete**: Final refactor of layout, routing, and page loading structure
- **Phase 2.5.1 Complete**: Final cleanup of routing and layout includes
### Major Architecture Changes
- **Modular Structure**: All admin pages physically modularized by module
- **Component System**: UI components (forms, filters, tables, modals) extracted into reusable templates
- **Static Routing**: Eliminated dynamic routing, converted to static file includes
- **Layout Standardization**: All pages follow `ob_start() → $igny8_page_content → global-layout.php` pattern
- **Submodule System**: Complete subpage structure for planner, writer, thinker, settings, help modules
### Technical Improvements
- **Configuration-Driven UI**: Tables, forms, and filters generated dynamically from config files
- **Complete Component Loading**: All submodules now include filters, actions, table, and pagination
- **JavaScript Integration**: Proper localization and data setup for all submodules
- **Debug Isolation**: Development files moved to dedicated folders with proper guards
- **Help Module**: Centralized help, documentation, and testing functionality
### Files Restructured
- **Modules**: `/modules/planner/`, `/modules/writer/`, `/modules/thinker/`, `/modules/settings/`, `/modules/help/`
- **Components**: `/modules/components/` with reusable UI templates
- **Config**: `/modules/config/` with centralized configuration arrays
- **Core**: `/core/` with layout, admin, database, and cron functionality
- **AI**: `/ai/` with content generation and image processing
### Database & Configuration
- **Table Configurations**: Complete table structure definitions in `tables-config.php`
- **Filter Configurations**: Dynamic filter system in `filters-config.php`
- **Import/Export**: Centralized import/export configurations
- **KPI System**: Dashboard metrics and analytics configuration
### Developer Experience
- **File Organization**: Clear separation of concerns and modular architecture
- **Documentation**: Comprehensive documentation and troubleshooting guides
- **Debug Tools**: System testing and function testing interfaces
- **Code Standards**: Consistent file headers and scope declarations
## [5.3.0] - 2025-01-15
### Critical Cron vs Manual Function Analysis
- **CRITICAL DISCREPANCY IDENTIFIED**: Cron functions have significant differences from manual counterparts
- **Function Dependency Issues**: Cron handlers include extensive fallback logic for functions like `igny8_get_sector_options()`
- **User Context Problems**: Cron handlers manually set admin user context while manual AJAX handlers rely on authenticated user
- **Warning Suppression**: Cron handlers suppress PHP warnings that manual handlers don't, potentially masking issues
- **Database Connection**: Cron handlers explicitly declare `global $wpdb` while manual handlers use it directly
- **Risk Assessment**: Cron functions are at HIGH RISK of failing or behaving differently than manual functions
### Technical Analysis Findings
- **Auto Cluster**: Manual `igny8_ajax_ai_cluster_keywords()` vs Cron `igny8_auto_cluster_cron_handler()`
- **Auto Ideas**: Manual `igny8_ajax_ai_generate_ideas()` vs Cron `igny8_auto_generate_ideas_cron_handler()`
- **Auto Queue**: Manual `igny8_ajax_queue_ideas_to_writer()` vs Cron `igny8_auto_queue_cron_handler()`
- **Auto Content**: Manual `igny8_ajax_ai_generate_content()` vs Cron `igny8_auto_generate_content_cron_handler()`
- **Auto Image**: Manual `igny8_ajax_ai_generate_images_drafts()` vs Cron `igny8_auto_generate_images_cron_handler()`
- **Auto Publish**: Manual `igny8_ajax_bulk_publish_drafts()` vs Cron `igny8_auto_publish_drafts_cron_handler()`

View File

@@ -0,0 +1,14 @@
<?php
/**
* ==============================
* 📁 Folder Scope Declaration
* ==============================
* Folder: /ai/
* Purpose: AI content/image logic, parsers, prompt APIs
* Rules:
* - Can be reused globally across all modules
* - Contains all AI integration logic
* - OpenAI API integration and management
* - AI prompt libraries and templates
* - AI model configuration and rate limiting
*/

View File

@@ -0,0 +1,21 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : integration.php
* @location : /ai/integration.php
* @type : AI Integration
* @scope : Global
* @allowed : API configuration, connection management, authentication
* @reusability : Globally Reusable
* @notes : AI service configuration and connection management
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Include Runware API integration
require_once plugin_dir_path(__FILE__) . 'runware-api.php';

View File

@@ -0,0 +1,192 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : model-rates-config.php
* @location : /ai/model-rates-config.php
* @type : Config Array
* @scope : Global
* @allowed : Model pricing, cost calculations, rate configurations
* @reusability : Globally Reusable
* @notes : Central AI model pricing configuration
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Global model rates configuration
* Rates are per 1 million tokens for text models
* Per image for image generation models
* GPT-4.1, GPT-4o-mini, and GPT-4o models are supported
*/
$IGNY8_MODEL_RATES = [
'gpt-4.1' => ['in' => 2.00, 'out' => 8.00],
'gpt-4o-mini' => ['in' => 0.15, 'out' => 0.60],
'gpt-4o' => ['in' => 2.50, 'out' => 10.00]
];
/**
* Global image model rates configuration
* Rates are per image
*/
$IGNY8_IMAGE_MODEL_RATES = [
'dall-e-3' => 0.040,
'dall-e-2' => 0.020,
'gpt-image-1' => 0.042,
'gpt-image-1-mini' => 0.011
];
/**
* Get model rates for a specific model
*
* @param string $model Model name
* @return array Model rates array with 'in' and 'out' keys
*/
function igny8_get_model_rates($model) {
global $IGNY8_MODEL_RATES;
return $IGNY8_MODEL_RATES[$model] ?? $IGNY8_MODEL_RATES['gpt-4.1'];
}
/**
* Calculate API cost based on model and token usage
*
* @param string $model Model name
* @param int $input_tokens Number of input tokens
* @param int $output_tokens Number of output tokens
* @return array Cost breakdown with 'input_cost', 'output_cost', 'total_cost'
*/
function igny8_calculate_api_cost($model, $input_tokens, $output_tokens) {
$rates = igny8_get_model_rates($model);
// Debug logging
error_log("Igny8 Cost Calc Debug: Model=$model, Rates=" . json_encode($rates));
error_log("Igny8 Cost Calc Debug: Input tokens=$input_tokens, Output tokens=$output_tokens");
$input_cost = ($input_tokens / 1000000) * $rates['in'];
$output_cost = ($output_tokens / 1000000) * $rates['out'];
$total_cost = $input_cost + $output_cost;
error_log("Igny8 Cost Calc Debug: Input cost=$input_cost, Output cost=$output_cost, Total cost=$total_cost");
return [
'input_cost' => $input_cost,
'output_cost' => $output_cost,
'total_cost' => $total_cost,
'model' => $model,
'input_tokens' => $input_tokens,
'output_tokens' => $output_tokens
];
}
/**
* Format cost for display
*
* @param float $cost Cost amount
* @param int $decimals Number of decimal places
* @return string Formatted cost string
*/
function igny8_format_cost($cost, $decimals = 4) {
// Convert to cents for better readability
$cents = $cost * 100;
return number_format($cents, 2) . '¢';
}
/**
* Get image model rates for a specific model
*
* @param string $model Image model name
* @return float Image model rate per image
*/
function igny8_get_image_model_rates($model) {
global $IGNY8_IMAGE_MODEL_RATES;
return $IGNY8_IMAGE_MODEL_RATES[$model] ?? $IGNY8_IMAGE_MODEL_RATES['dall-e-3'];
}
/**
* Calculate image generation cost based on model
*
* @param string $model Image model name
* @param int $image_count Number of images generated
* @return array Cost breakdown with 'per_image_cost', 'total_cost'
*/
function igny8_calculate_image_cost($model, $image_count = 1) {
$per_image_rate = igny8_get_image_model_rates($model);
$total_cost = $per_image_rate * $image_count;
return [
'per_image_cost' => $per_image_rate,
'total_cost' => $total_cost,
'model' => $model,
'image_count' => $image_count
];
}
/**
* Get image model display name with pricing and typical uses
*
* @param string $model Image model name
* @return string Formatted model name with pricing and uses
*/
function igny8_get_image_model_display_name($model) {
$model_info = [
'dall-e-3' => [
'name' => 'DALL·E 3',
'uses' => 'High-quality image generation with advanced AI capabilities'
],
'dall-e-2' => [
'name' => 'DALL·E 2',
'uses' => 'Cost-effective image generation with good quality'
],
'gpt-image-1' => [
'name' => 'GPT Image 1 (Full)',
'uses' => 'Full-featured image generation with comprehensive capabilities'
],
'gpt-image-1-mini' => [
'name' => 'GPT Image 1 Mini',
'uses' => 'Lightweight, cost-effective image generation for bulk operations'
]
];
$rate = igny8_get_image_model_rates($model);
$info = $model_info[$model] ?? ['name' => strtoupper($model), 'uses' => 'Image generation'];
return sprintf(
'%s — $%.3f per image (%s)',
$info['name'], $rate, $info['uses']
);
}
/**
* Get model display name with pricing and typical uses
*
* @param string $model Model name
* @return string Formatted model name with pricing and uses
*/
function igny8_get_model_display_name($model) {
$model_info = [
'gpt-4.1' => [
'name' => 'GPT-4.1',
'uses' => 'Content creation, coding, analysis, high-quality content generation'
],
'gpt-4o-mini' => [
'name' => 'GPT-4o mini',
'uses' => 'Bulk tasks, lightweight AI, cost-effective for high-volume operations'
],
'gpt-4o' => [
'name' => 'GPT-4o',
'uses' => 'Advanced AI for general and multimodal tasks, faster than GPT-4.1'
]
];
$rates = igny8_get_model_rates($model);
$info = $model_info[$model] ?? ['name' => strtoupper($model), 'uses' => 'General purpose'];
return sprintf(
'%s — $%.2f / $%.2f per 1M tokens (%s)',
$info['name'], $rates['in'], $rates['out'], $info['uses']
);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,310 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : prompts-library.php
* @location : /ai/prompts-library.php
* @type : AI Integration
* @scope : Global
* @allowed : AI prompts, prompt management, AI templates
* @reusability : Globally Reusable
* @notes : Central AI prompts library for all modules
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Default clustering prompt template
*/
function igny8_get_default_clustering_prompt() {
return "Analyze the following keywords and group them into topic clusters.
Each cluster should include:
- \"name\": A clear, descriptive topic name
- \"description\": A brief explanation of what the cluster covers
- \"keywords\": A list of related keywords that belong to this cluster
Format the output as a JSON object with a \"clusters\" array.
Clustering rules:
- Group keywords based on strong semantic or topical relationships (intent, use-case, function, audience, etc.)
- Clusters should reflect how people actually search — problem ➝ solution, general ➝ specific, product ➝ benefit, etc.
- Avoid grouping keywords just because they share similar words — focus on meaning
- Include 310 keywords per cluster where appropriate
- Skip unrelated or outlier keywords that don't fit a clear theme
Keywords to process:
[IGNY8_KEYWORDS]";
}
/**
* Default ideas prompt template
*/
function igny8_get_default_ideas_prompt() {
return "Generate SEO-optimized, high-quality content ideas and detailed outlines for each of the following keyword clusters. Each idea must be valuable for SEO, have clear editorial flow, and include a structured long-form content outline that matches the standards of our content generation system.
==========================
CONTENT IDEA INPUT FORMAT
==========================
Clusters to analyze:
[IGNY8_CLUSTERS]
Keywords in each cluster:
[IGNY8_CLUSTER_KEYWORDS]
======================
OUTPUT FORMAT REQUIRED
======================
Return your response as JSON with an \"ideas\" array.
For each cluster, you must generate exactly 1 cluster_hub page and 24 supporting blog/article ideas based on unique keyword dimensions.
Each idea must include:
- \"title\": compelling blog/article title that naturally includes a primary keyword
- \"description\": detailed and structured content outline using proper H2/H3 breakdowns (see outline rules below)
- \"content_type\": the type of content (post, page)
- \"content_structure\": the editorial structure (cluster_hub, guide_tutorial, how_to, comparison, review, top_listicle, question)
- \"cluster_id\": ID of the cluster this idea belongs to
- \"estimated_word_count\": estimated total word count (range: 15002200 words)
- \"covered_keywords\": comma-separated list of keywords from the cluster that will be covered naturally in the content
=========================
OUTLINE (DESCRIPTION) RULES
=========================
Each content idea's \"description\" must follow the expected editorial structure based on both the content_type and content_structure fields. It should be formatted as a professional long-form content outline, suitable for direct use by AI or human writers.
1. STRUCTURE:
- INTRODUCTION SECTION: Start with 1 hook (italic, 30-40 words) followed by 2 intro paragraphs (50-60 words each)
- Use exactly 58 H2 sections (depending on content type)
- Each H2 section should contain 23 H3 subsections
- SECTION WORD COUNT: Each H2 section should be 250-300 words total
- CONTENT MIX: Vary content types within sections:
- Some sections: 1 paragraph + list/table + 1 paragraph
- Some sections: 2 paragraphs + list/table (as final element)
- Mix unordered lists, ordered lists, and tables strategically
- Use diverse formatting styles: data/stat mentions, expert quotes, comparative breakdowns
2. FORMATTING TYPES:
- content_type: \"paragraph\", \"list\", \"table\", \"blockquote\", or \"mixed\"
- Do not open sections or subheadings with bullet points or generic phrasing
- Use bullet lists or tables only after sufficient paragraph setup
- Tables should have defined columns (e.g., Feature, Benefit, Product Name, Price, Link)
- Blockquotes should contain expert insight, best practices, or unique POV
- Ensure each section varies in tone and structure from others
3. QUALITY & DEPTH:
- Write for depth and uniqueness, not surface-level filler
- Use primary keyword in title, intro, and at least 2 H2 sections
- Use secondary keywords naturally throughout outline
- Suggest informative, useful angles that solve a real problem or offer unique value
- Avoid repeated structure across all ideas — each outline should feel hand-edited
==========================
TONE & FLOW REQUIREMENTS
==========================
- Maintain a professional, editorial tone — not robotic or templated
- No generic intros like \"In today's article…\"
- Begin sections with direct, engaging sentences — not summaries of the heading
- Use real-world examples, relevant context, or recent data wherever useful
- Include ideas that could incorporate user intent like tutorials, comparisons, or decision support
==========================
FINAL RETURN FORMAT (JSON)
==========================
{
\"ideas\": [
{
\"title\": \"Best Organic Cotton Duvet Covers for All Seasons\",
\"description\": {
\"introduction\": {
\"hook\": \"Transform your sleep with organic cotton that blends comfort and sustainability.\",
\"paragraphs\": [
{
\"content_type\": \"paragraph\",
\"details\": \"Overview of organic cotton's rise in bedding industry.\"
},
{
\"content_type\": \"paragraph\",
\"details\": \"Why consumers prefer organic bedding over synthetic alternatives.\"
}
]
},
\"H2\": [
{
\"heading\": \"Why Choose Organic Cotton for Bedding?\",
\"subsections\": [
{
\"subheading\": \"Health and Skin Benefits\",
\"content_type\": \"paragraph\",
\"details\": \"Discuss hypoallergenic and chemical-free aspects.\"
},
{
\"subheading\": \"Environmental Sustainability\",
\"content_type\": \"list\",
\"details\": \"Bullet list of eco benefits like low water use, no pesticides.\"
},
{
\"subheading\": \"Long-Term Cost Savings\",
\"content_type\": \"table\",
\"details\": \"Table comparing durability and pricing over time.\"
}
]
},
{
\"heading\": \"Top Organic Cotton Duvet Brands\",
\"subsections\": [
{
\"subheading\": \"Brand 1 Overview\",
\"content_type\": \"paragraph\",
\"details\": \"Description of features, pricing, and audience fit.\"
},
{
\"subheading\": \"Brand 2 Overview\",
\"content_type\": \"paragraph\",
\"details\": \"Highlight what makes this brand stand out.\"
},
{
\"subheading\": \"Quick Comparison Table\",
\"content_type\": \"table\",
\"details\": \"Side-by-side feature and price comparison.\"
}
]
}
]
},
\"content_type\": \"post\",
\"content_structure\": \"review\",
\"cluster_id\": 12,
\"estimated_word_count\": 1800,
\"covered_keywords\": \"organic duvet covers, eco-friendly bedding, sustainable sheets\"
}
]
}
==============================
NOTES FOR EXECUTION ENGINE
==============================
- Make sure all outlines follow this structure and are unique in flow and format.
- Do not reuse same outline patterns across ideas.
- Emphasize depth, paragraph-first formatting, and mixed content presentation.
- Ensure final output is usable by long-form AI content systems and human writers alike.";
}
/**
* Default content generation prompt template
*/
function igny8_content_generation_prompt() {
return "You are an editorial content strategist. Your task is to generate a complete JSON response object that includes all the fields listed below, based on the provided content idea, keyword cluster, and keyword list.
Only the `content` field should contain HTML. All other fields must be plain JSON values.
==================
CONTENT OBJECT STRUCTURE
==================
{
\"title\": \"[Auto-generate a compelling blog title using the primary keyword]\",
\"meta_title\": \"[SEO-optimized meta title under 60 characters]\",
\"meta_description\": \"[SEO-friendly summary under 160 characters]\",
\"content\": \"[HTML body — see structure rules below]\",
\"word_count\": [Exact word count of the HTML content],
\"primary_keyword\": \"[Provided primary keyword]\",
\"secondary_keywords\": [List of provided secondary keywords],
\"keywords_used\": [List of all keywords actually used in the HTML content],
\"tags\": [List of 5 lowercase tags (24 words each)],
\"categories\": [\"Parent > Child\", \"Optional 2nd category if needed\"],
[IMAGE_PROMPTS]
}
===========================
CONTENT FORMAT & STRUCTURE
===========================
- Use only valid WP-supported HTML blocks: <h2>, <h3>, <p>, <ul>/<ol>, and <table>
- Do not add extra line breaks, empty tags, or inconsistent spacing
- Use proper table structure when using tables:
<table>
<thead>
<tr><th>col heading1</th><th>col heading2</th></tr>
</thead>
<tbody>
<tr>
<td>cell1</td><td>cell2</td>
</tr>
</tbody>
</table>
===========================
CONTENT FLOW RULES
===========================
**INTRODUCTION:**
- Start with 1 italicized hook (3040 words)
- Follow with 2 narrative paragraphs (each 5060 words; 23 sentences max)
- No headings allowed in intro
**H2 SECTIONS (58 total):**
Each section should be 250300 words and follow this format:
1. Two narrative paragraphs (80120 words each, 23 sentences)
2. One list or table (must come *after* a paragraph)
3. Optional closing paragraph (4060 words)
4. Insert 23 <h3> subsections naturally after main paragraphs
**Formatting Rules:**
- Vary use of unordered lists, ordered lists, and tables across sections
- Never begin any section or sub-section with a list or table
===========================
KEYWORD & SEO RULES
===========================
- **Primary keyword** must appear in:
- The title
- First paragraph of the introduction
- At least 2 H2 headings
- **Secondary keywords** must be used naturally, not forced
- **Tone & style guidelines:**
- No robotic or passive voice
- Avoid generic intros like \"In today's world…\"
- Don't repeat heading in opening sentence
- Vary sentence structure and length
===========================
IMAGE PROMPT RULES
===========================
- Provide detailed, specific, and relevant image prompts
- Each prompt should reflect the topic/subtopic in that section
- Avoid vague or generic prompts
===========================
INPUT VARIABLES
===========================
CONTENT IDEA DETAILS:
[IGNY8_IDEA]
KEYWORD CLUSTER:
[IGNY8_CLUSTER]
ASSOCIATED KEYWORDS:
[IGNY8_KEYWORDS]
===========================
OUTPUT FORMAT
===========================
Return ONLY the final JSON object.
Do NOT include any comments, formatting, or explanations.";
}

View File

@@ -0,0 +1,191 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : runware-api.php
* @location : /ai/runware-api.php
* @type : AI Integration
* @scope : Global
* @allowed : Runware API calls, image generation, AI processing
* @reusability : Globally Reusable
* @notes : Runware image generation API integration
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Generate image using Runware API
*
* @param string $prompt The image generation prompt
* @param string $model The Runware model to use (default: gen3a_turbo)
* @return array|WP_Error Response data or error
*/
function igny8_runway_generate_image($prompt, $model = 'gen3a_turbo') {
$api_key = get_option('igny8_runware_api_key', '');
if (empty($api_key)) {
return new WP_Error('no_api_key', 'Runware API key not configured');
}
$url = 'https://api.runwayml.com/v1/image/generations';
$headers = [
'Authorization' => 'Bearer ' . $api_key,
'Content-Type' => 'application/json',
];
$body = [
'model' => $model,
'prompt' => $prompt,
'size' => '1024x1024',
'quality' => 'standard',
'n' => 1
];
$args = [
'method' => 'POST',
'headers' => $headers,
'body' => json_encode($body),
'timeout' => 60,
];
$response = wp_remote_post($url, $args);
if (is_wp_error($response)) {
igny8_log_ai_event('runway_api_error', 'error', [
'message' => $response->get_error_message(),
'prompt' => $prompt,
'model' => $model
]);
return $response;
}
$response_code = wp_remote_retrieve_response_code($response);
$response_body = wp_remote_retrieve_body($response);
$response_data = json_decode($response_body, true);
if ($response_code !== 200) {
$error_message = isset($response_data['error']['message']) ? $response_data['error']['message'] : 'Unknown error';
igny8_log_ai_event('runway_api_error', 'error', [
'code' => $response_code,
'message' => $error_message,
'prompt' => $prompt,
'model' => $model
]);
return new WP_Error('api_error', $error_message);
}
// Log successful API call
igny8_log_ai_event('runway_api_success', 'success', [
'model' => $model,
'prompt_length' => strlen($prompt),
'cost' => 0.036, // Runware pricing
'service' => 'runware'
]);
return $response_data;
}
/**
* Download and save image from Runware response
*
* @param array $response_data The API response data
* @param string $filename The desired filename
* @return string|WP_Error Saved file path or error
*/
function igny8_runway_save_image($response_data, $filename) {
if (!isset($response_data['data'][0]['url'])) {
return new WP_Error('no_image_url', 'No image URL in response');
}
$image_url = $response_data['data'][0]['url'];
// Create uploads directory
$upload_dir = wp_upload_dir();
$igny8_dir = $upload_dir['basedir'] . '/igny8-ai-images/';
if (!file_exists($igny8_dir)) {
wp_mkdir_p($igny8_dir);
}
// Download image
$image_response = wp_remote_get($image_url);
if (is_wp_error($image_response)) {
return $image_response;
}
$image_data = wp_remote_retrieve_body($image_response);
$file_path = $igny8_dir . $filename;
$saved = file_put_contents($file_path, $image_data);
if ($saved === false) {
return new WP_Error('save_failed', 'Failed to save image file');
}
return $file_path;
}
/**
* Test Runware API connection
*
* @return array Test result
*/
function igny8_test_runway_connection() {
$test_prompt = 'A simple test image: a red circle on white background';
$response = igny8_runway_generate_image($test_prompt);
if (is_wp_error($response)) {
return [
'success' => false,
'message' => $response->get_error_message(),
'details' => 'Runware API connection failed'
];
}
return [
'success' => true,
'message' => 'Runware API connection successful',
'details' => 'Test image generation completed successfully'
];
}
/**
* Get available Runware models
*
* @return array Available models
*/
function igny8_get_runway_models() {
return [
'gen3a_turbo' => [
'name' => 'Gen-3 Alpha Turbo',
'description' => 'Fast, high-quality image generation',
'cost' => 0.055
],
'gen3a' => [
'name' => 'Gen-3 Alpha',
'description' => 'Standard quality image generation',
'cost' => 0.055
]
];
}
/**
* Log AI event for Runway API
*
* @param string $event Event type
* @param string $status Success/error status
* @param array $context Additional context data
*/
function igny8_log_runway_event($event, $status, $context = []) {
igny8_log_ai_event($event, $status, array_merge($context, [
'service' => 'runware',
'timestamp' => current_time('mysql')
]));
}

View File

@@ -0,0 +1,534 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : image-generation.php
* @location : /ai/writer/images/image-generation.php
* @type : AI Integration
* @scope : Global
* @allowed : Image generation, AI processing, media creation
* @reusability : Globally Reusable
* @notes : Image generation functions for all modules
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Generate featured image for post from post meta prompt
*
* @param int $post_id WordPress post ID
* @param string $image_size_type Type of image size to use (featured, desktop, mobile)
* @return array Result with success status and attachment_id or error
*/
function igny8_generate_featured_image_for_post($post_id, $image_size_type = 'featured') {
// Get post
$post = get_post($post_id);
if (!$post) {
return ['success' => false, 'error' => 'Post not found'];
}
// Get featured image prompt from post meta
$featured_image_prompt = get_post_meta($post_id, '_igny8_featured_image_prompt', true);
if (empty($featured_image_prompt)) {
return ['success' => false, 'error' => 'No featured image prompt found in post meta'];
}
// Get image generation settings from prompts page
$image_type = get_option('igny8_image_type', 'realistic');
$image_service = get_option('igny8_image_service', 'openai');
$image_format = get_option('igny8_image_format', 'jpg');
$negative_prompt = get_option('igny8_negative_prompt', 'text, watermark, logo, overlay, title, caption, writing on walls, writing on objects, UI, infographic elements, post title');
// Get image model settings based on service
$image_model = get_option('igny8_image_model', 'dall-e-3');
$runware_model = get_option('igny8_runware_model', 'runware:97@1');
$prompt_template = wp_unslash(get_option('igny8_image_prompt_template', 'Create a high-quality {image_type} image to use as a featured photo for a blog post titled "{post_title}". The image should visually represent the theme, mood, and subject implied by the image prompt: {image_prompt}. Focus on a realistic, well-composed scene that naturally communicates the topic without text or logos. Use balanced lighting, pleasing composition, and photographic detail suitable for lifestyle or editorial web content. Avoid adding any visible or readable text, brand names, or illustrative effects. **And make sure image is not blurry.**'));
// Get dimensions based on image size type and service
$dimensions = igny8_get_image_dimensions($image_size_type, $image_service);
$image_width = $dimensions['width'];
$image_height = $dimensions['height'];
// Get API keys
$openai_key = get_option('igny8_api_key', '');
$runware_key = get_option('igny8_runware_api_key', '');
$required_key = ($image_service === 'runware') ? $runware_key : $openai_key;
if (empty($required_key)) {
return ['success' => false, 'error' => ($image_service === 'runware' ? 'Runware' : 'OpenAI') . ' API key not configured'];
}
// Build final prompt
$prompt = str_replace(
['{image_type}', '{post_title}', '{image_prompt}'],
[$image_type, $post->post_title, $featured_image_prompt],
$prompt_template
);
try {
// Event 7: API request sent
error_log('Igny8: IMAGE_GEN_EVENT_7 - API request sent to ' . $image_service . ' for post: ' . $post_id);
if ($image_service === 'runware') {
// Runware API Call
$payload = [
[
'taskType' => 'authentication',
'apiKey' => $runware_key
],
[
'taskType' => 'imageInference',
'taskUUID' => wp_generate_uuid4(),
'positivePrompt' => $prompt,
'negativePrompt' => $negative_prompt,
'model' => $runware_model,
'width' => $image_width,
'height' => $image_height,
'steps' => 30,
'CFGScale' => 7.5,
'numberResults' => 1,
'outputFormat' => $image_format
]
];
$response = wp_remote_post('https://api.runware.ai/v1', [
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode($payload),
'timeout' => 150, // Increased to 150 seconds for image generation
'httpversion' => '1.1',
'sslverify' => true
]);
if (is_wp_error($response)) {
error_log('Igny8: IMAGE_GEN_ERROR - Runware API request failed: ' . $response->get_error_message());
return ['success' => false, 'error' => 'Runware API Error: ' . $response->get_error_message()];
}
$response_body = wp_remote_retrieve_body($response);
$response_data = json_decode($response_body, true);
error_log('Igny8: IMAGE_GEN - Runware API response: ' . substr($response_body, 0, 200));
if (isset($response_data['data'][0]['imageURL'])) {
$image_url = $response_data['data'][0]['imageURL'];
// Event 8: Image URL received
error_log('Igny8: IMAGE_GEN_EVENT_8 - Image URL received from ' . $image_service . ' for post: ' . $post_id);
// Generate filename
$filename = sanitize_file_name($post->post_title) . '_featured_' . time() . '.' . $image_format;
// Event 9: Image saved to WordPress
error_log('Igny8: IMAGE_GEN_EVENT_9 - Saving image to WordPress for post: ' . $post_id);
// Download image from Runware URL
require_once(ABSPATH . 'wp-admin/includes/media.php');
require_once(ABSPATH . 'wp-admin/includes/file.php');
require_once(ABSPATH . 'wp-admin/includes/image.php');
error_log('Igny8: IMAGE_GEN - Downloading image from URL: ' . $image_url);
$temp_file = download_url($image_url);
if (is_wp_error($temp_file)) {
error_log('Igny8: IMAGE_GEN_EVENT_9_ERROR - Failed to download image: ' . $temp_file->get_error_message());
return ['success' => false, 'error' => 'Failed to download image: ' . $temp_file->get_error_message()];
}
error_log('Igny8: IMAGE_GEN - Image downloaded to temp file: ' . $temp_file);
// Prepare file array for media_handle_sideload
$file_array = [
'name' => $filename,
'tmp_name' => $temp_file
];
// Upload to WordPress media library
error_log('Igny8: IMAGE_GEN - Uploading to media library with media_handle_sideload');
$attachment_id = media_handle_sideload($file_array, $post_id, $post->post_title . ' Featured Image');
// Clean up temp file
if (file_exists($temp_file)) {
@unlink($temp_file);
}
if (is_wp_error($attachment_id)) {
error_log('Igny8: IMAGE_GEN_EVENT_9_ERROR - media_handle_sideload failed: ' . $attachment_id->get_error_message());
return ['success' => false, 'error' => 'Failed to upload image: ' . $attachment_id->get_error_message()];
}
error_log('Igny8: IMAGE_GEN - Successfully created attachment ID: ' . $attachment_id);
if (!is_wp_error($attachment_id)) {
// Set as featured image
set_post_thumbnail($post_id, $attachment_id);
// Generate attachment metadata
$attachment_data = wp_generate_attachment_metadata($attachment_id, get_attached_file($attachment_id));
wp_update_attachment_metadata($attachment_id, $attachment_data);
// Get attachment URL
$attachment_url = wp_get_attachment_url($attachment_id);
error_log('Igny8: IMAGE_GEN_EVENT_9_SUCCESS - Image saved successfully, attachment ID: ' . $attachment_id);
return [
'success' => true,
'attachment_id' => $attachment_id,
'image_url' => $attachment_url,
'provider' => 'runware'
];
} else {
error_log('Igny8: IMAGE_GEN_EVENT_9_ERROR - Failed to save image: ' . $attachment_id->get_error_message());
return ['success' => false, 'error' => 'Failed to register image: ' . $attachment_id->get_error_message()];
}
} else {
error_log('Igny8: IMAGE_GEN_EVENT_8_ERROR - No image URL in response');
$error_msg = isset($response_data['errors'][0]['message']) ? $response_data['errors'][0]['message'] : 'Unknown Runware API error';
return ['success' => false, 'error' => $error_msg];
}
} else {
// OpenAI API Call with selected model
$data = [
'model' => $image_model,
'prompt' => $prompt,
'n' => 1,
'size' => $image_width . 'x' . $image_height
];
$args = [
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $openai_key,
],
'body' => json_encode($data),
'timeout' => 60,
];
$response = wp_remote_post('https://api.openai.com/v1/images/generations', $args);
if (is_wp_error($response)) {
return ['success' => false, 'error' => 'OpenAI API Error: ' . $response->get_error_message()];
}
$response_code = wp_remote_retrieve_response_code($response);
$response_body = wp_remote_retrieve_body($response);
if ($response_code === 200) {
$body = json_decode($response_body, true);
if (isset($body['data'][0]['url'])) {
$image_url = $body['data'][0]['url'];
$revised_prompt = $body['data'][0]['revised_prompt'] ?? null;
// Generate filename (OpenAI always returns PNG)
$filename = sanitize_file_name($post->post_title) . '_featured_' . time() . '.png';
// Download and register in WordPress Media Library
$attachment_id = wp_insert_attachment([
'post_mime_type' => 'image/png',
'post_title' => $post->post_title . ' Featured Image',
'post_content' => '',
'post_status' => 'inherit'
], $image_url, $post_id);
if (!is_wp_error($attachment_id)) {
// Set as featured image
set_post_thumbnail($post_id, $attachment_id);
// Get attachment URL
$attachment_url = wp_get_attachment_url($attachment_id);
return [
'success' => true,
'attachment_id' => $attachment_id,
'image_url' => $attachment_url,
'provider' => 'openai',
'revised_prompt' => $revised_prompt
];
} else {
return ['success' => false, 'error' => 'Failed to register image: ' . $attachment_id->get_error_message()];
}
}
} else {
return ['success' => false, 'error' => 'HTTP ' . $response_code . ' error'];
}
}
} catch (Exception $e) {
return ['success' => false, 'error' => 'Exception: ' . $e->getMessage()];
}
return ['success' => false, 'error' => 'Unknown error occurred'];
}
/**
* Generate single article image for post from post meta prompts
*
* @param int $post_id WordPress post ID
* @param string $device_type Device type: 'desktop' or 'mobile'
* @param int $index Image index (1-based)
* @return array Result with success status and attachment_id or error
*/
function igny8_generate_single_article_image($post_id, $device_type = 'desktop', $index = 1) {
// Get post
$post = get_post($post_id);
if (!$post) {
return ['success' => false, 'error' => 'Post not found'];
}
// Get article image prompts from post meta
$article_images_data = get_post_meta($post_id, '_igny8_article_images_data', true);
// Debug: Log the raw data to see what's actually stored
error_log('IGNY8 DEBUG: Raw article_images_data: ' . substr($article_images_data, 0, 200) . '...');
$article_images = json_decode($article_images_data, true);
// Check for JSON decode errors
if (json_last_error() !== JSON_ERROR_NONE) {
error_log('IGNY8 DEBUG: JSON decode error: ' . json_last_error_msg());
error_log('IGNY8 DEBUG: Raw data causing error: ' . $article_images_data);
// Try to clean the data by stripping HTML tags
$cleaned_data = wp_strip_all_tags($article_images_data);
$article_images = json_decode($cleaned_data, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log('IGNY8 DEBUG: Still invalid JSON after cleaning: ' . json_last_error_msg());
return ['success' => false, 'error' => 'Invalid JSON in article images data: ' . json_last_error_msg()];
} else {
error_log('IGNY8 DEBUG: Successfully cleaned and parsed JSON');
}
}
if (empty($article_images) || !is_array($article_images)) {
return ['success' => false, 'error' => 'No article image prompts found in post meta'];
}
// Find the prompt for the requested index
$image_key = 'prompt-img-' . $index;
$prompt = '';
foreach ($article_images as $image_data) {
if (isset($image_data[$image_key])) {
$prompt = $image_data[$image_key];
break;
}
}
if (empty($prompt)) {
return ['success' => false, 'error' => 'No prompt found for image index ' . $index];
}
// Get image generation settings
$image_type = get_option('igny8_image_type', 'realistic');
$image_service = get_option('igny8_image_service', 'openai');
$image_format = get_option('igny8_image_format', 'jpg');
$negative_prompt = get_option('igny8_negative_prompt', 'text, watermark, logo, overlay, title, caption, writing on walls, writing on objects, UI, infographic elements, post title');
// Get image model settings based on service
$image_model = get_option('igny8_image_model', 'dall-e-3');
$runware_model = get_option('igny8_runware_model', 'runware:97@1');
// Get dimensions based on device type and service
$dimensions = igny8_get_image_dimensions($device_type, $image_service);
$image_width = $dimensions['width'];
$image_height = $dimensions['height'];
// Get API keys
$openai_key = get_option('igny8_api_key', '');
$runware_key = get_option('igny8_runware_api_key', '');
$required_key = ($image_service === 'runware') ? $runware_key : $openai_key;
if (empty($required_key)) {
return ['success' => false, 'error' => ($image_service === 'runware' ? 'Runware' : 'OpenAI') . ' API key not configured'];
}
// Enhance prompt if needed
$full_prompt = $prompt;
if (strlen($prompt) < 50 || strpos($prompt, 'Create') !== 0) {
$section = "Section " . $index;
$full_prompt = "Create a high-quality {$image_type} image for the section titled '{$section}'. {$prompt}";
}
try {
error_log('Igny8: ARTICLE_IMAGE_GEN - Generating ' . $device_type . ' image for post: ' . $post_id . ', index: ' . $index);
if ($image_service === 'runware') {
// Runware API Call
$payload = [
[
'taskType' => 'authentication',
'apiKey' => $runware_key
],
[
'taskType' => 'imageInference',
'taskUUID' => wp_generate_uuid4(),
'positivePrompt' => $full_prompt,
'negativePrompt' => $negative_prompt,
'model' => $runware_model,
'width' => $image_width,
'height' => $image_height,
'steps' => 30,
'CFGScale' => 7.5,
'numberResults' => 1,
'outputFormat' => $image_format
]
];
$response = wp_remote_post('https://api.runware.ai/v1', [
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode($payload),
'timeout' => 150,
'httpversion' => '1.1',
'sslverify' => true
]);
if (is_wp_error($response)) {
error_log('Igny8: ARTICLE_IMAGE_GEN_ERROR - Runware API request failed: ' . $response->get_error_message());
return ['success' => false, 'error' => 'Runware API Error: ' . $response->get_error_message()];
}
$response_body = wp_remote_retrieve_body($response);
$response_data = json_decode($response_body, true);
if (isset($response_data['data'][0]['imageURL'])) {
$image_url = $response_data['data'][0]['imageURL'];
// Generate filename
$filename = sanitize_file_name($post->post_title) . '_' . $device_type . '_' . $index . '_' . time() . '.' . $image_format;
// Download image from Runware URL
require_once(ABSPATH . 'wp-admin/includes/media.php');
require_once(ABSPATH . 'wp-admin/includes/file.php');
require_once(ABSPATH . 'wp-admin/includes/image.php');
$temp_file = download_url($image_url);
if (is_wp_error($temp_file)) {
return ['success' => false, 'error' => 'Failed to download image: ' . $temp_file->get_error_message()];
}
// Prepare file array for media_handle_sideload
$file_array = [
'name' => $filename,
'tmp_name' => $temp_file
];
// Upload to WordPress media library
$attachment_id = media_handle_sideload($file_array, $post_id, $post->post_title . ' ' . ucfirst($device_type) . ' Image ' . $index);
// Clean up temp file
if (file_exists($temp_file)) {
@unlink($temp_file);
}
if (is_wp_error($attachment_id)) {
return ['success' => false, 'error' => 'Failed to upload image: ' . $attachment_id->get_error_message()];
}
// Generate attachment metadata
$attachment_data = wp_generate_attachment_metadata($attachment_id, get_attached_file($attachment_id));
wp_update_attachment_metadata($attachment_id, $attachment_data);
// Add custom metadata
update_post_meta($attachment_id, '_igny8_image_type', $device_type);
update_post_meta($attachment_id, '_igny8_provider', 'runware');
update_post_meta($attachment_id, '_igny8_section', 'Section ' . $index);
// Get attachment URL
$attachment_url = wp_get_attachment_url($attachment_id);
error_log('Igny8: ARTICLE_IMAGE_GEN_SUCCESS - ' . $device_type . ' image generated, attachment ID: ' . $attachment_id);
return [
'success' => true,
'attachment_id' => $attachment_id,
'image_url' => $attachment_url,
'provider' => 'runware',
'device_type' => $device_type,
'index' => $index
];
} else {
$error_msg = isset($response_data['errors'][0]['message']) ? $response_data['errors'][0]['message'] : 'Unknown Runware API error';
return ['success' => false, 'error' => $error_msg];
}
} else {
// OpenAI API Call with selected model
$data = [
'model' => $image_model,
'prompt' => $full_prompt,
'n' => 1,
'size' => '1024x1024' // OpenAI only supports square
];
$args = [
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $openai_key,
],
'body' => json_encode($data),
'timeout' => 60,
];
$response = wp_remote_post('https://api.openai.com/v1/images/generations', $args);
if (is_wp_error($response)) {
return ['success' => false, 'error' => 'OpenAI API Error: ' . $response->get_error_message()];
}
$response_code = wp_remote_retrieve_response_code($response);
$response_body = wp_remote_retrieve_body($response);
if ($response_code === 200) {
$body = json_decode($response_body, true);
if (isset($body['data'][0]['url'])) {
$image_url = $body['data'][0]['url'];
$revised_prompt = $body['data'][0]['revised_prompt'] ?? null;
// Generate filename (OpenAI always returns PNG)
$filename = sanitize_file_name($post->post_title) . '_' . $device_type . '_' . $index . '_' . time() . '.png';
// Download and register in WordPress Media Library
$attachment_id = wp_insert_attachment([
'post_mime_type' => 'image/png',
'post_title' => $post->post_title . ' ' . ucfirst($device_type) . ' Image ' . $index,
'post_content' => '',
'post_status' => 'inherit'
], $image_url, $post_id);
if (!is_wp_error($attachment_id)) {
// Add custom metadata
update_post_meta($attachment_id, '_igny8_image_type', $device_type);
update_post_meta($attachment_id, '_igny8_provider', 'openai');
update_post_meta($attachment_id, '_igny8_section', 'Section ' . $index);
// Get attachment URL
$attachment_url = wp_get_attachment_url($attachment_id);
return [
'success' => true,
'attachment_id' => $attachment_id,
'image_url' => $attachment_url,
'provider' => 'openai',
'device_type' => $device_type,
'index' => $index,
'revised_prompt' => $revised_prompt
];
} else {
return ['success' => false, 'error' => 'Failed to register image: ' . $attachment_id->get_error_message()];
}
}
} else {
return ['success' => false, 'error' => 'HTTP ' . $response_code . ' error'];
}
}
} catch (Exception $e) {
return ['success' => false, 'error' => 'Exception: ' . $e->getMessage()];
}
return ['success' => false, 'error' => 'Unknown error occurred'];
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,51 @@
/**
* Igny8 Image Injection CSS
*
* Responsive image display styles for marker-based image injection
*
* @package Igny8
* @version 1.0.0
*/
/* Desktop and larger screens (769px+) */
@media (min-width: 769px) {
.igny8-article-image-desktop {
display: block !important;
}
.igny8-article-image-mobile {
display: none !important;
}
}
/* Mobile and smaller screens (768px and below) */
@media (max-width: 768px) {
.igny8-article-image-desktop {
display: none !important;
}
.igny8-article-image-mobile {
display: block !important;
}
}
/* Image wrapper styling */
.igny8-image-wrapper {
margin: 20px 0;
text-align: center;
}
.igny8-image-wrapper img {
max-width: 100%;
height: auto;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* Loading state */
.igny8-image-wrapper img[loading="lazy"] {
opacity: 0;
transition: opacity 0.3s ease;
}
.igny8-image-wrapper img[loading="lazy"].loaded {
opacity: 1;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,436 @@
/**
* Igny8 Image Queue Processor
* Sequential image generation with individual progress tracking
*/
// Process AI Image Generation for Drafts (Sequential Image Processing with Queue Modal)
function processAIImageGenerationDrafts(postIds) {
console.log('Igny8: processAIImageGenerationDrafts called with postIds:', postIds);
// Event 1: Generate Images button clicked
if (window.addImageGenDebugLog) {
window.addImageGenDebugLog('INFO', 'Generate Images button clicked', {
postIds: postIds,
timestamp: new Date().toISOString()
});
}
// Get image generation settings from saved options (passed via wp_localize_script)
const desktopEnabled = window.IGNY8_PAGE?.imageSettings?.desktop_enabled || false;
const mobileEnabled = window.IGNY8_PAGE?.imageSettings?.mobile_enabled || false;
const maxInArticleImages = window.IGNY8_PAGE?.imageSettings?.max_in_article_images || 1;
// Event 2: Settings retrieved
if (window.addImageGenDebugLog) {
window.addImageGenDebugLog('SUCCESS', 'Settings retrieved', {
desktop: desktopEnabled,
mobile: mobileEnabled,
maxImages: maxInArticleImages
});
}
// Build image queue based on settings
const imageQueue = [];
postIds.forEach((postId, postIndex) => {
// Featured image (always)
imageQueue.push({
post_id: postId,
post_number: postIndex + 1,
type: 'featured',
device: '',
label: 'Featured Image',
post_title: `Post ${postIndex + 1}`
});
// Desktop in-article images
if (desktopEnabled) {
for (let i = 1; i <= maxInArticleImages; i++) {
imageQueue.push({
post_id: postId,
post_number: postIndex + 1,
type: 'article',
device: 'desktop',
index: i,
label: `desktop-${i}`,
section: i,
post_title: `Post ${postIndex + 1}`
});
}
}
// Mobile in-article images
if (mobileEnabled) {
for (let i = 1; i <= maxInArticleImages; i++) {
imageQueue.push({
post_id: postId,
post_number: postIndex + 1,
type: 'article',
device: 'mobile',
index: i,
label: `mobile-${i}`,
section: i,
post_title: `Post ${postIndex + 1}`
});
}
}
});
console.log('Igny8: Image queue built:', imageQueue);
// Show queue modal
showImageQueueModal(imageQueue, imageQueue.length);
// Start processing queue
processImageQueue(imageQueue, 0);
}
// Show modal with image queue and individual progress bars
function showImageQueueModal(queue, totalImages) {
if (window.currentProgressModal) {
window.currentProgressModal.remove();
}
const modal = document.createElement('div');
modal.id = 'igny8-image-queue-modal';
modal.className = 'igny8-modal';
let queueHTML = '';
queue.forEach((item, index) => {
const itemId = `queue-item-${index}`;
queueHTML += `
<div class="queue-item" id="${itemId}" data-status="pending">
<div style="display: flex; gap: 10px; align-items: center;">
<div style="flex: 1;">
<div class="queue-item-header">
<span class="queue-number">${index + 1}</span>
<span class="queue-label">${item.label}</span>
<span class="queue-post-title">${item.post_title}</span>
<span class="queue-status">⏳ Pending</span>
</div>
<div class="queue-progress-bar">
<div class="queue-progress-fill" style="width: 0%"></div>
<div class="queue-progress-text">0%</div>
</div>
<div class="queue-error" style="display: none;"></div>
</div>
<div class="queue-thumbnail" style="width: 75px; height: 75px; background: #f0f0f0; border-radius: 4px; display: flex; align-items: center; justify-content: center; overflow: hidden; flex-shrink: 0;">
<span style="color: #999; font-size: 11px;">No image</span>
</div>
</div>
</div>
`;
});
modal.innerHTML = `
<div class="igny8-modal-content" style="max-width: 950px; max-height: 80vh; overflow-y: auto;">
<div class="igny8-modal-header">
<h3>🎨 Generating Images</h3>
<p style="margin: 5px 0; color: var(--text-light);">Total: ${totalImages} images in queue</p>
</div>
<div class="igny8-modal-body">
<div class="image-queue-container">
${queueHTML}
</div>
</div>
</div>
<style>
.queue-item {
margin-bottom: 12px;
padding: 12px;
background: var(--panel-2);
border-radius: 8px;
border: 1px solid var(--border);
}
.queue-item[data-status="processing"] {
background: rgba(59, 130, 246, 0.1);
border-color: rgb(59, 130, 246);
}
.queue-item[data-status="completed"] {
background: rgba(16, 185, 129, 0.1);
border-color: rgb(16, 185, 129);
}
.queue-item[data-status="failed"] {
background: rgba(239, 68, 68, 0.1);
border-color: rgb(239, 68, 68);
}
.queue-item-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
font-size: 13px;
}
.queue-number {
background: rgb(59, 130, 246);
color: white;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: bold;
}
.queue-label {
font-weight: 600;
}
.queue-post-title {
flex: 1;
font-size: 12px;
opacity: 0.7;
}
.queue-status {
font-size: 12px;
font-weight: 600;
}
.queue-progress-bar {
height: 20px;
background: rgba(0,0,0,0.1);
border-radius: 10px;
overflow: hidden;
position: relative;
}
.queue-progress-fill {
height: 100%;
background: rgb(59, 130, 246);
transition: width 0.3s ease;
}
.queue-progress-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 11px;
font-weight: bold;
color: #333;
text-shadow: 0 0 3px rgba(255,255,255,0.8);
}
.queue-item[data-status="completed"] .queue-progress-fill {
background: rgb(16, 185, 129);
}
.queue-item[data-status="failed"] .queue-progress-fill {
background: rgb(239, 68, 68);
}
.queue-error {
margin-top: 8px;
padding: 8px;
background: rgba(239, 68, 68, 0.1);
border-left: 3px solid rgb(239, 68, 68);
border-radius: 4px;
font-size: 12px;
}
</style>
`;
document.body.appendChild(modal);
modal.classList.add('open');
window.currentProgressModal = modal;
}
// Process image queue sequentially with progressive loading
function processImageQueue(queue, currentIndex) {
if (currentIndex >= queue.length) {
// All done
console.log('Igny8: All images processed');
// Log to Image Generation Debug
if (window.addImageGenDebugLog) {
window.addImageGenDebugLog('SUCCESS', 'All images processed', {
total: queue.length,
timestamp: new Date().toISOString()
});
}
setTimeout(() => {
if (window.currentProgressModal) {
window.currentProgressModal.remove();
window.currentProgressModal = null;
}
showNotification('Image generation complete!', 'success');
// Reload table
if (window.loadTableData && window.IGNY8_PAGE?.tableId) {
window.loadTableData(window.IGNY8_PAGE.tableId);
}
}, 2000);
return;
}
const item = queue[currentIndex];
const itemElement = document.getElementById(`queue-item-${currentIndex}`);
if (!itemElement) {
console.error('Queue item element not found:', currentIndex);
// Log to Image Generation Debug
if (window.addImageGenDebugLog) {
window.addImageGenDebugLog('ERROR', 'Queue item element not found', {
index: currentIndex,
itemId: `queue-item-${currentIndex}`
});
}
setTimeout(() => processImageQueue(queue, currentIndex + 1), 100);
return;
}
// Update UI to processing
itemElement.setAttribute('data-status', 'processing');
itemElement.querySelector('.queue-status').textContent = '⏳ Generating...';
const progressFill = itemElement.querySelector('.queue-progress-fill');
const progressText = itemElement.querySelector('.queue-progress-text');
// Log to Image Generation Debug
if (window.addImageGenDebugLog) {
window.addImageGenDebugLog('INFO', `Processing ${item.label}`, {
postId: item.post_id,
type: item.type,
device: item.device || 'N/A',
index: item.index || 1,
queuePosition: `${currentIndex + 1}/${queue.length}`
});
}
// Progressive loading: 50% in 7s, 75% in next 5s, then 5% every second until 95%
let currentProgress = 0;
let phase = 1;
let phaseStartTime = Date.now();
const progressInterval = setInterval(() => {
const elapsed = Date.now() - phaseStartTime;
if (phase === 1 && currentProgress < 50) {
// Phase 1: 0% to 50% in 7 seconds (7.14% per second)
currentProgress += 0.714;
if (currentProgress >= 50 || elapsed >= 7000) {
currentProgress = 50;
phase = 2;
phaseStartTime = Date.now();
}
} else if (phase === 2 && currentProgress < 75) {
// Phase 2: 50% to 75% in 5 seconds (5% per second)
currentProgress += 0.5;
if (currentProgress >= 75 || elapsed >= 5000) {
currentProgress = 75;
phase = 3;
phaseStartTime = Date.now();
}
} else if (phase === 3 && currentProgress < 95) {
// Phase 3: 75% to 95% - 5% every second
if (elapsed >= 1000) {
currentProgress = Math.min(95, currentProgress + 5);
phaseStartTime = Date.now();
}
}
progressFill.style.width = currentProgress + '%';
progressText.textContent = Math.round(currentProgress) + '%';
}, 100);
// Generate single image
const formData = new FormData();
formData.append('action', 'igny8_ai_generate_single_image');
formData.append('nonce', window.IGNY8_PAGE.nonce);
formData.append('post_id', item.post_id);
formData.append('type', item.type);
formData.append('device', item.device || '');
formData.append('index', item.index || 1);
// Add meta box integration fields
formData.append('image_label', item.label || '');
formData.append('section', item.section || '');
fetch(window.IGNY8_PAGE.ajaxUrl, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
// Stop progressive loading
clearInterval(progressInterval);
if (data.success) {
// Success - complete to 100%
progressFill.style.width = '100%';
progressText.textContent = '100%';
itemElement.setAttribute('data-status', 'completed');
itemElement.querySelector('.queue-status').textContent = '✅ Complete';
// Display thumbnail if image URL is available
if (data.data?.image_url) {
const thumbnailDiv = itemElement.querySelector('.queue-thumbnail');
if (thumbnailDiv) {
thumbnailDiv.innerHTML = `<img src="${data.data.image_url}" style="width: 100%; height: 100%; object-fit: cover; border-radius: 4px;" alt="Generated image">`;
}
}
console.log(`✓ Image ${currentIndex + 1} generated successfully`);
// Log to Image Generation Debug
if (window.addImageGenDebugLog) {
window.addImageGenDebugLog('SUCCESS', `${item.label} generated successfully`, {
postId: item.post_id,
attachmentId: data.data?.attachment_id,
provider: data.data?.provider,
queuePosition: `${currentIndex + 1}/${queue.length}`
});
}
// Process next image after short delay
setTimeout(() => processImageQueue(queue, currentIndex + 1), 500);
} else {
// Error - show at 90%
progressFill.style.width = '90%';
progressText.textContent = 'Failed';
itemElement.setAttribute('data-status', 'failed');
itemElement.querySelector('.queue-status').textContent = '❌ Failed';
const errorDiv = itemElement.querySelector('.queue-error');
errorDiv.textContent = data.data?.message || 'Unknown error';
errorDiv.style.display = 'block';
console.error(`✗ Image ${currentIndex + 1} failed:`, data.data?.message);
// Log to Image Generation Debug
if (window.addImageGenDebugLog) {
window.addImageGenDebugLog('ERROR', `${item.label} generation failed`, {
postId: item.post_id,
error: data.data?.message || 'Unknown error',
queuePosition: `${currentIndex + 1}/${queue.length}`
});
}
// Continue to next image despite error
setTimeout(() => processImageQueue(queue, currentIndex + 1), 1000);
}
})
.catch(error => {
// Exception - stop progressive loading
clearInterval(progressInterval);
progressFill.style.width = '90%';
progressText.textContent = 'Error';
itemElement.setAttribute('data-status', 'failed');
itemElement.querySelector('.queue-status').textContent = '❌ Error';
const errorDiv = itemElement.querySelector('.queue-error');
errorDiv.textContent = 'Exception: ' + error.message;
errorDiv.style.display = 'block';
console.error(`✗ Image ${currentIndex + 1} exception:`, error);
// Log to Image Generation Debug
if (window.addImageGenDebugLog) {
window.addImageGenDebugLog('ERROR', `${item.label} request exception`, {
postId: item.post_id,
error: error.message,
queuePosition: `${currentIndex + 1}/${queue.length}`
});
}
// Continue to next image despite error
setTimeout(() => processImageQueue(queue, currentIndex + 1), 1000);
});
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* ==============================
* 📁 Folder Scope Declaration
* ==============================
* Folder: /shortcodes/
* Purpose: All shortcode handler files (split by module)
* Rules:
* - Must be organized by module
* - Each shortcode must be self-contained
* - No cross-module dependencies
* - Must use components for rendering
* - Frontend-only functionality
*/

View File

@@ -0,0 +1,278 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : image-gallery.php
* @location : /assets/shortcodes/image-gallery.php
* @type : Shortcode
* @scope : Global
* @allowed : Shortcode registration, frontend rendering, image display
* @reusability : Globally Reusable
* @notes : Image gallery shortcodes for frontend display
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Display specific in-article image by ID
*
* Usage: [igny8-image id="desktop-1"]
*
* @param array $atts Shortcode attributes
* @return string HTML output
*/
add_shortcode('igny8-image', function($atts) {
$atts = shortcode_atts(['id' => ''], $atts);
$post_id = get_the_ID();
if (empty($post_id)) {
return '';
}
$images = get_post_meta($post_id, '_igny8_inarticle_images', true);
if (!is_array($images)) {
return '';
}
// Display specific image by ID
if (!empty($atts['id']) && isset($images[$atts['id']])) {
$image_data = $images[$atts['id']];
// Device detection - only show if device matches
$is_mobile = wp_is_mobile();
$is_desktop = !$is_mobile;
// Check if image should be displayed based on device
$should_display = false;
if (strpos($atts['id'], 'desktop-') === 0 && $is_desktop) {
$should_display = true;
} elseif (strpos($atts['id'], 'mobile-') === 0 && $is_mobile) {
$should_display = true;
}
if (!$should_display) {
return '';
}
$attachment_id = intval($image_data['attachment_id']);
if ($attachment_id > 0) {
return wp_get_attachment_image($attachment_id, 'large', false, [
'class' => 'igny8-inarticle-image',
'data-image-id' => esc_attr($atts['id']),
'data-device' => esc_attr($image_data['device']),
'alt' => esc_attr($image_data['label'])
]);
}
}
return '';
});
/**
* Display all in-article images
*
* Usage: [igny8-images]
*
* @param array $atts Shortcode attributes
* @return string HTML output
*/
add_shortcode('igny8-images', function($atts) {
$atts = shortcode_atts([
'device' => '', // Filter by device type (desktop/mobile)
'size' => 'large', // Image size
'class' => 'igny8-image-gallery' // CSS class
], $atts);
$post_id = get_the_ID();
if (empty($post_id)) {
return '';
}
$images = get_post_meta($post_id, '_igny8_inarticle_images', true);
if (!is_array($images) || empty($images)) {
return '';
}
$output = '<div class="' . esc_attr($atts['class']) . '">';
$output .= '<p style="background: #f0f0f0; padding: 10px; border-left: 4px solid #0073aa; margin: 10px 0; font-weight: bold;">This is coming from shortcode</p>';
foreach ($images as $label => $image_data) {
// Filter by device if specified
if (!empty($atts['device']) && $image_data['device'] !== $atts['device']) {
continue;
}
$attachment_id = intval($image_data['attachment_id']);
if ($attachment_id > 0) {
$output .= wp_get_attachment_image($attachment_id, $atts['size'], false, [
'class' => 'igny8-inarticle-image',
'data-image-id' => esc_attr($label),
'data-device' => esc_attr($image_data['device']),
'alt' => esc_attr($image_data['label'])
]);
}
}
$output .= '</div>';
return $output;
});
/**
* Display desktop images only
*
* Usage: [igny8-desktop-images]
*
* @param array $atts Shortcode attributes
* @return string HTML output
*/
add_shortcode('igny8-desktop-images', function($atts) {
$atts = shortcode_atts([
'size' => 'large',
'class' => 'igny8-desktop-gallery'
], $atts);
return do_shortcode('[igny8-images device="desktop" size="' . $atts['size'] . '" class="' . $atts['class'] . '"]');
});
/**
* Display mobile images only
*
* Usage: [igny8-mobile-images]
*
* @param array $atts Shortcode attributes
* @return string HTML output
*/
add_shortcode('igny8-mobile-images', function($atts) {
$atts = shortcode_atts([
'size' => 'large',
'class' => 'igny8-mobile-gallery'
], $atts);
return do_shortcode('[igny8-images device="mobile" size="' . $atts['size'] . '" class="' . $atts['class'] . '"]');
});
/**
* Display image count
*
* Usage: [igny8-image-count]
*
* @param array $atts Shortcode attributes
* @return string HTML output
*/
add_shortcode('igny8-image-count', function($atts) {
$atts = shortcode_atts(['device' => ''], $atts);
$post_id = get_the_ID();
if (empty($post_id)) {
return '0';
}
$images = get_post_meta($post_id, '_igny8_inarticle_images', true);
if (!is_array($images)) {
return '0';
}
if (!empty($atts['device'])) {
$count = 0;
foreach ($images as $image_data) {
if ($image_data['device'] === $atts['device']) {
$count++;
}
}
return (string) $count;
}
return (string) count($images);
});
/**
* Display image gallery with responsive design
*
* Usage: [igny8-responsive-gallery]
*
* @param array $atts Shortcode attributes
* @return string HTML output
*/
add_shortcode('igny8-responsive-gallery', function($atts) {
$atts = shortcode_atts([
'desktop_size' => 'large',
'mobile_size' => 'medium',
'class' => 'igny8-responsive-gallery'
], $atts);
$post_id = get_the_ID();
if (empty($post_id)) {
return '';
}
$images = get_post_meta($post_id, '_igny8_inarticle_images', true);
if (!is_array($images) || empty($images)) {
return '';
}
$output = '<div class="' . esc_attr($atts['class']) . '">';
// Desktop images
$desktop_images = array_filter($images, function($img) {
return $img['device'] === 'desktop';
});
if (!empty($desktop_images)) {
$output .= '<div class="igny8-desktop-images" style="display: block;">';
foreach ($desktop_images as $label => $image_data) {
$attachment_id = intval($image_data['attachment_id']);
if ($attachment_id > 0) {
$output .= wp_get_attachment_image($attachment_id, $atts['desktop_size'], false, [
'class' => 'igny8-desktop-image',
'data-image-id' => esc_attr($label)
]);
}
}
$output .= '</div>';
}
// Mobile images
$mobile_images = array_filter($images, function($img) {
return $img['device'] === 'mobile';
});
if (!empty($mobile_images)) {
$output .= '<div class="igny8-mobile-images" style="display: none;">';
foreach ($mobile_images as $label => $image_data) {
$attachment_id = intval($image_data['attachment_id']);
if ($attachment_id > 0) {
$output .= wp_get_attachment_image($attachment_id, $atts['mobile_size'], false, [
'class' => 'igny8-mobile-image',
'data-image-id' => esc_attr($label)
]);
}
}
$output .= '</div>';
}
$output .= '</div>';
// Add responsive CSS
$output .= '<style>
@media (max-width: 768px) {
.igny8-desktop-images { display: none !important; }
.igny8-mobile-images { display: block !important; }
}
@media (min-width: 769px) {
.igny8-desktop-images { display: block !important; }
.igny8-mobile-images { display: none !important; }
}
</style>';
return $output;
});

View File

@@ -0,0 +1,4 @@
cluster_name,sector_id,status,keyword_count,total_volume,avg_difficulty,mapped_pages_count
"Car Interior Accessories",1,"active",25,45000,42,0
"Car Storage Solutions",1,"active",18,32000,38,0
"Car Beverage Holders",1,"active",12,18000,35,0
1 cluster_name sector_id status keyword_count total_volume avg_difficulty mapped_pages_count
2 Car Interior Accessories 1 active 25 45000 42 0
3 Car Storage Solutions 1 active 18 32000 38 0
4 Car Beverage Holders 1 active 12 18000 35 0

View File

@@ -0,0 +1,4 @@
idea_title,idea_description,content_structure,content_type,keyword_cluster_id,target_keywords,status,estimated_word_count
"Top 10 Car Interior Accessories for 2024","A comprehensive list of the best car interior accessories available this year, including reviews and recommendations.","review","post",1,"car accessories, car storage solutions, car interior accessories","new",1200
"How to Organize Your Car Interior Like a Pro","Step-by-step guide to organizing your car interior for maximum efficiency and comfort.","guide_tutorial","post",2,"car organization, car storage tips, car interior organization","new",1500
"DIY Car Storage Solutions That Actually Work","Creative and practical DIY storage solutions you can make at home for your car.","guide_tutorial","post",2,"DIY car storage, car storage solutions, car organization tips","new",800
1 idea_title idea_description content_structure content_type keyword_cluster_id target_keywords status estimated_word_count
2 Top 10 Car Interior Accessories for 2024 A comprehensive list of the best car interior accessories available this year, including reviews and recommendations. review post 1 car accessories, car storage solutions, car interior accessories new 1200
3 How to Organize Your Car Interior Like a Pro Step-by-step guide to organizing your car interior for maximum efficiency and comfort. guide_tutorial post 2 car organization, car storage tips, car interior organization new 1500
4 DIY Car Storage Solutions That Actually Work Creative and practical DIY storage solutions you can make at home for your car. guide_tutorial post 2 DIY car storage, car storage solutions, car organization tips new 800

View File

@@ -0,0 +1,4 @@
keyword,search_volume,difficulty,cpc,intent,status,sector_id,cluster_id
"car accessories",12000,45,2.50,"commercial","unmapped",1,0
"car storage solutions",8500,38,1.80,"informational","unmapped",1,0
"car interior accessories",15000,52,3.20,"commercial","unmapped",1,0
1 keyword search_volume difficulty cpc intent status sector_id cluster_id
2 car accessories 12000 45 2.50 commercial unmapped 1 0
3 car storage solutions 8500 38 1.80 informational unmapped 1 0
4 car interior accessories 15000 52 3.20 commercial unmapped 1 0

View File

@@ -0,0 +1,14 @@
<?php
/**
* ==============================
* 📁 Folder Scope Declaration
* ==============================
* Folder: /core/
* Purpose: Layout, init, DB, CRON - Core system functionality
* Rules:
* - Can be reused globally across all modules
* - Contains WordPress integration logic
* - Database operations and schema management
* - Admin interface and routing
* - CRON and automation systems
*/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,135 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : init.php
* @location : /core/admin/init.php
* @type : Function Library
* @scope : Global
* @allowed : Admin initialization, settings registration, asset enqueuing
* @reusability : Globally Reusable
* @notes : WordPress admin initialization and settings management
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* ------------------------------------------------------------------------
* ADMIN INITIALIZATION BOOTSTRAP
* ------------------------------------------------------------------------
*/
add_action('admin_init', 'igny8_register_settings');
/**
* ------------------------------------------------------------------------
* SETTINGS REGISTRATION
* ------------------------------------------------------------------------
*/
function igny8_register_settings() {
$groups = igny8_get_settings_config();
foreach ($groups as $group => $settings) {
foreach ($settings as $name => $config) {
register_setting($group, $name, $config);
}
}
}
/**
* Settings Configuration (grouped)
*/
function igny8_get_settings_config() {
return [
'igny8_table_settings' => [
'igny8_records_per_page' => [
'type' => 'integer',
'default' => 20,
'sanitize_callback' => 'absint'
]
],
'igny8_ai_integration_settings' => [
'igny8_ai_cluster_building' => ['type' => 'string', 'default' => 'enabled', 'sanitize_callback' => 'sanitize_text_field'],
'igny8_ai_content_ideas' => ['type' => 'string', 'default' => 'enabled', 'sanitize_callback' => 'sanitize_text_field'],
'igny8_ai_auto_mapping' => ['type' => 'string', 'default' => 'enabled', 'sanitize_callback' => 'sanitize_text_field']
],
'igny8_api_settings' => [
'igny8_api_key' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field'],
'igny8_runware_api_key' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field'],
'igny8_model' => ['type' => 'string', 'default' => 'gpt-4.1', 'sanitize_callback' => 'sanitize_text_field'],
'igny8_image_service' => ['type' => 'string', 'default' => 'openai', 'sanitize_callback' => 'sanitize_text_field'],
'igny8_image_model' => ['type' => 'string', 'default' => 'dall-e-3', 'sanitize_callback' => 'sanitize_text_field'],
'igny8_runware_model' => ['type' => 'string', 'default' => 'runware:97@1', 'sanitize_callback' => 'sanitize_text_field']
],
'igny8_personalize_settings_group' => [
'igny8_content_engine_global_status' => ['sanitize_callback' => 'igny8_sanitize_checkbox_setting'],
'igny8_content_engine_enabled_post_types' => ['sanitize_callback' => 'igny8_sanitize_array_setting'],
'igny8_content_engine_insertion_position' => ['sanitize_callback' => 'sanitize_text_field'],
'igny8_content_engine_display_mode' => ['sanitize_callback' => 'sanitize_text_field'],
'igny8_content_engine_teaser_text' => ['sanitize_callback' => 'sanitize_textarea_field'],
'igny8_content_engine_save_variations' => ['sanitize_callback' => 'intval'],
'igny8_content_engine_field_mode' => ['sanitize_callback' => 'sanitize_text_field'],
'igny8_content_engine_detection_prompt' => ['sanitize_callback' => 'sanitize_textarea_field'],
'igny8_content_engine_context_source' => ['sanitize_callback' => 'sanitize_textarea_field'],
'igny8_content_engine_include_page_context' => ['sanitize_callback' => 'intval'],
'igny8_content_engine_content_length' => ['sanitize_callback' => 'sanitize_text_field'],
'igny8_content_engine_rewrite_prompt' => ['sanitize_callback' => 'sanitize_textarea_field'],
'igny8_content_engine_fixed_fields_config' => ['sanitize_callback' => 'igny8_sanitize_fields_config']
]
];
}
/**
* ------------------------------------------------------------------------
* SANITIZATION HELPERS
* ------------------------------------------------------------------------
*/
function igny8_sanitize_checkbox_setting($raw) {
return isset($_POST['igny8_content_engine_global_status']) ? 'enabled' : 'disabled';
}
function igny8_sanitize_array_setting($raw) {
return is_array($raw) ? array_map('sanitize_text_field', $raw) : [];
}
function igny8_sanitize_fields_config($raw) {
if (!is_array($raw)) return [];
$sanitized = [];
foreach ($raw as $index => $field) {
$sanitized[$index] = [
'label' => sanitize_text_field($field['label'] ?? ''),
'type' => sanitize_text_field($field['type'] ?? 'text'),
'options' => sanitize_text_field($field['options'] ?? '')
];
}
return $sanitized;
}
// MOVED TO: igny8.php - Admin assets enqueuing moved to main plugin file
// ---------------------------------------------------------------------
// WORDPRESS FEATURE REGISTRATION
// ---------------------------------------------------------------------
function igny8_init_wordpress_features() {
// Initialize module manager
add_action('init', 'igny8_module_manager');
// Register taxonomies
add_action('init', 'igny8_register_taxonomies');
// Register post meta once
add_action('init', function() {
if (!get_option('igny8_post_meta_registered')) {
igny8_register_post_meta();
update_option('igny8_post_meta_registered', true);
}
});
}
//Initialize WordPress features
igny8_init_wordpress_features();

View File

@@ -0,0 +1,356 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : menu.php
* @location : /core/admin/menu.php
* @type : Admin Menu Handler
* @scope : Global
* @allowed : WordPress admin menu registration, navigation helpers, layout functions
* @reusability : Globally Reusable
* @notes : Registers admin menus and provides breadcrumb/submenu rendering functions
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Render breadcrumb navigation
*/
function igny8_render_breadcrumb() {
$current_page = $_GET['page'] ?? '';
$sm = $_GET['sm'] ?? '';
$breadcrumb = '<nav class="igny8-breadcrumb-nav">';
$breadcrumb .= '<span class="igny8-breadcrumb-item"><a href="' . admin_url('admin.php?page=igny8-home') . '">Igny8 Home</a></span>';
if ($current_page === 'igny8-planner') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item"><a href="' . admin_url('admin.php?page=igny8-planner') . '">Planner</a></span>';
if ($sm === 'keywords') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Keywords</span>';
} elseif ($sm === 'clusters') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Clusters</span>';
} elseif ($sm === 'ideas') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Ideas</span>';
} elseif ($sm === 'mapping') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Mapping</span>';
}
} elseif ($current_page === 'igny8-writer') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item"><a href="' . admin_url('admin.php?page=igny8-writer') . '">Writer</a></span>';
if ($sm === 'drafts') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Drafts</span>';
} elseif ($sm === 'templates') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Templates</span>';
}
} elseif ($current_page === 'igny8-optimizer') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item"><a href="' . admin_url('admin.php?page=igny8-optimizer') . '">Optimizer</a></span>';
if ($sm === 'audits') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Audits</span>';
} elseif ($sm === 'suggestions') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Suggestions</span>';
}
} elseif ($current_page === 'igny8-linker') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item"><a href="' . admin_url('admin.php?page=igny8-linker') . '">Linker</a></span>';
if ($sm === 'backlinks') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Backlinks</span>';
} elseif ($sm === 'campaigns') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Campaigns</span>';
}
} elseif ($current_page === 'igny8-personalize') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item"><a href="' . admin_url('admin.php?page=igny8-personalize') . '">Personalize</a></span>';
if ($sm === 'settings') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Settings</span>';
} elseif ($sm === 'content-generation') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Content Generation</span>';
} elseif ($sm === 'rewrites') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Rewrites</span>';
} elseif ($sm === 'front-end') {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Front-end</span>';
}
} elseif (strpos($current_page, 'igny8-analytics') !== false) {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Analytics</span>';
} elseif (strpos($current_page, 'igny8-schedules') !== false) {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Schedules</span>';
} elseif (strpos($current_page, 'igny8-settings') !== false) {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Settings</span>';
} elseif (strpos($current_page, 'igny8-help') !== false) {
$breadcrumb .= '<span class="igny8-breadcrumb-separator"></span>';
$breadcrumb .= '<span class="igny8-breadcrumb-item active">Help</span>';
}
$breadcrumb .= '</nav>';
return $breadcrumb;
}
/**
* Render submenu navigation
*/
function igny8_render_submenu() {
$current_page = $_GET['page'] ?? '';
$sm = $_GET['sm'] ?? '';
$submenu = '';
if ($current_page === 'igny8-planner') {
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-planner&sm=home') . '" class="igny8-btn igny8-btn-sm igny8-btn-success igny8-btn-submenu' . ($sm === 'home' || $sm === '' ? ' active' : '') . '">Dashboard</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-planner&sm=keywords') . '" class="igny8-btn igny8-btn-sm igny8-btn-success igny8-btn-submenu' . ($sm === 'keywords' ? ' active' : '') . '">Keywords</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-planner&sm=clusters') . '" class="igny8-btn igny8-btn-sm igny8-btn-success igny8-btn-submenu' . ($sm === 'clusters' ? ' active' : '') . '">Clusters</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-planner&sm=ideas') . '" class="igny8-btn igny8-btn-sm igny8-btn-success igny8-btn-submenu' . ($sm === 'ideas' ? ' active' : '') . '">Ideas</a>';
} elseif ($current_page === 'igny8-writer') {
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-writer&sm=home') . '" class="igny8-btn igny8-btn-sm igny8-btn-primary igny8-btn-submenu' . ($sm === 'home' || $sm === '' ? ' active' : '') . '">Dashboard</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-writer&sm=tasks') . '" class="igny8-btn igny8-btn-sm igny8-btn-primary igny8-btn-submenu' . ($sm === 'tasks' ? ' active' : '') . '">Tasks</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-writer&sm=drafts') . '" class="igny8-btn igny8-btn-sm igny8-btn-primary igny8-btn-submenu' . ($sm === 'drafts' ? ' active' : '') . '">Drafts</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-writer&sm=published') . '" class="igny8-btn igny8-btn-sm igny8-btn-primary igny8-btn-submenu' . ($sm === 'published' ? ' active' : '') . '">Published</a>';
} elseif ($current_page === 'igny8-thinker') {
$sp = $_GET['sp'] ?? 'main';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-thinker&sp=main') . '" class="igny8-btn igny8-btn-sm igny8-btn-outline igny8-btn-submenu' . ($sp === 'main' ? ' active' : '') . '">Dashboard</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-thinker&sp=prompts') . '" class="igny8-btn igny8-btn-sm igny8-btn-outline igny8-btn-submenu' . ($sp === 'prompts' ? ' active' : '') . '">Prompts</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-thinker&sp=profile') . '" class="igny8-btn igny8-btn-sm igny8-btn-outline igny8-btn-submenu' . ($sp === 'profile' ? ' active' : '') . '">Profile</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-thinker&sp=strategies') . '" class="igny8-btn igny8-btn-sm igny8-btn-outline igny8-btn-submenu' . ($sp === 'strategies' ? ' active' : '') . '">Strategies</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-thinker&sp=image-testing') . '" class="igny8-btn igny8-btn-sm igny8-btn-outline igny8-btn-submenu' . ($sp === 'image-testing' ? ' active' : '') . '">Image Testing</a>';
} elseif ($current_page === 'igny8-optimizer') {
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-optimizer') . '" class="igny8-btn igny8-btn-sm igny8-btn-warning igny8-btn-submenu' . ($sm === '' ? ' active' : '') . '">Dashboard</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-optimizer&sm=audits') . '" class="igny8-btn igny8-btn-sm igny8-btn-warning igny8-btn-submenu' . ($sm === 'audits' ? ' active' : '') . '">Audits</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-optimizer&sm=suggestions') . '" class="igny8-btn igny8-btn-sm igny8-btn-warning igny8-btn-submenu' . ($sm === 'suggestions' ? ' active' : '') . '">Suggestions</a>';
} elseif ($current_page === 'igny8-linker') {
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-linker') . '" class="igny8-btn igny8-btn-sm igny8-btn-info igny8-btn-submenu' . ($sm === '' ? ' active' : '') . '">Dashboard</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-linker&sm=backlinks') . '" class="igny8-btn igny8-btn-sm igny8-btn-info igny8-btn-submenu' . ($sm === 'backlinks' ? ' active' : '') . '">Backlinks</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-linker&sm=campaigns') . '" class="igny8-btn igny8-btn-sm igny8-btn-info igny8-btn-submenu' . ($sm === 'campaigns' ? ' active' : '') . '">Campaigns</a>';
} elseif ($current_page === 'igny8-personalize') {
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-personalize') . '" class="igny8-btn igny8-btn-sm igny8-btn-secondary igny8-btn-submenu' . ($sm === '' ? ' active' : '') . '">Dashboard</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-personalize&sm=settings') . '" class="igny8-btn igny8-btn-sm igny8-btn-secondary igny8-btn-submenu' . ($sm === 'settings' ? ' active' : '') . '">Settings</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-personalize&sm=content-generation') . '" class="igny8-btn igny8-btn-sm igny8-btn-secondary igny8-btn-submenu' . ($sm === 'content-generation' ? ' active' : '') . '">Content Generation</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-personalize&sm=rewrites') . '" class="igny8-btn igny8-btn-sm igny8-btn-secondary igny8-btn-submenu' . ($sm === 'rewrites' ? ' active' : '') . '">Rewrites</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-personalize&sm=front-end') . '" class="igny8-btn igny8-btn-sm igny8-btn-secondary igny8-btn-submenu' . ($sm === 'front-end' ? ' active' : '') . '">Front-end</a>';
} elseif ($current_page === 'igny8-settings') {
$sp = $_GET['sp'] ?? 'general';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-settings&sp=general') . '" class="igny8-btn igny8-btn-sm igny8-btn-outline igny8-btn-submenu' . ($sp === 'general' ? ' active' : '') . '">Settings</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-settings&sp=status') . '" class="igny8-btn igny8-btn-sm igny8-btn-outline igny8-btn-submenu' . ($sp === 'status' ? ' active' : '') . '">Status</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-settings&sp=integration') . '" class="igny8-btn igny8-btn-sm igny8-btn-outline igny8-btn-submenu' . ($sp === 'integration' ? ' active' : '') . '">Integration</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-settings&sp=import-export') . '" class="igny8-btn igny8-btn-sm igny8-btn-outline igny8-btn-submenu' . ($sp === 'import-export' ? ' active' : '') . '">Import/Export</a>';
} elseif ($current_page === 'igny8-help') {
$sp = $_GET['sp'] ?? 'help';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-help&sp=help') . '" class="igny8-btn igny8-btn-sm igny8-btn-outline igny8-btn-submenu' . ($sp === 'help' ? ' active' : '') . '">Help & Support</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-help&sp=docs') . '" class="igny8-btn igny8-btn-sm igny8-btn-outline igny8-btn-submenu' . ($sp === 'docs' ? ' active' : '') . '">Documentation</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-help&sp=system-testing') . '" class="igny8-btn igny8-btn-sm igny8-btn-outline igny8-btn-submenu' . ($sp === 'system-testing' ? ' active' : '') . '">System Testing</a>';
$submenu .= '<a href="' . admin_url('admin.php?page=igny8-help&sp=function-testing') . '" class="igny8-btn igny8-btn-sm igny8-btn-outline igny8-btn-submenu' . ($sp === 'function-testing' ? ' active' : '') . '">Function Testing</a>';
}
return $submenu;
}
/**
* Register admin menu pages
*/
function igny8_register_admin_menu() {
// Ensure module manager is available
if (!function_exists('igny8_is_module_enabled')) {
return;
}
// Main menu page
add_menu_page(
'Igny8 AI SEO', // Page title
'Igny8 AI SEO', // Menu title
'manage_options', // Capability
'igny8-home', // Menu slug
'igny8_home_page', // Callback function
'dashicons-chart-line', // Icon
30 // Position
);
// Home page
add_submenu_page(
'igny8-home', // Parent slug
'Dashboard', // Page title
'Dashboard', // Menu title
'manage_options', // Capability
'igny8-home', // Menu slug
'igny8_home_page' // Callback function
);
// Module submenus (only if enabled)
if (igny8_is_module_enabled('planner')) {
add_submenu_page(
'igny8-home',
'Content Planner',
'Planner',
'manage_options',
'igny8-planner',
'igny8_planner_page'
);
}
if (igny8_is_module_enabled('writer')) {
add_submenu_page(
'igny8-home',
'Content Writer',
'Writer',
'manage_options',
'igny8-writer',
'igny8_writer_page'
);
}
if (igny8_is_module_enabled('thinker')) {
add_submenu_page(
'igny8-home',
'AI Thinker',
'Thinker',
'manage_options',
'igny8-thinker',
'igny8_thinker_page'
);
// Prompts subpage under Thinker
add_submenu_page(
'igny8-thinker',
'AI Prompts',
'Prompts',
'manage_options',
'igny8-thinker&sp=prompts',
'igny8_thinker_page'
);
}
if (igny8_is_module_enabled('schedules')) {
add_submenu_page(
'igny8-home',
'Smart Automation Schedules',
'Schedules',
'manage_options',
'igny8-schedules',
'igny8_schedules_page'
);
}
// Analytics before Settings (only if enabled)
if (igny8_is_module_enabled('analytics')) {
add_submenu_page(
'igny8-home',
'Analytics',
'Analytics',
'manage_options',
'igny8-analytics',
'igny8_analytics_page'
);
}
// Cron Health page
// Settings page
add_submenu_page(
'igny8-home',
'Settings',
'Settings',
'manage_options',
'igny8-settings',
'igny8_settings_page'
);
// Help page
add_submenu_page(
'igny8-home',
'Help',
'Help',
'manage_options',
'igny8-help',
'igny8_help_page'
);
// Documentation subpage under Help
add_submenu_page(
'igny8-help',
'Documentation',
'Documentation',
'manage_options',
'igny8-help&sp=docs',
'igny8_help_page'
);
// System Testing subpage under Help
add_submenu_page(
'igny8-help',
'System Testing',
'System Testing',
'manage_options',
'igny8-help&sp=system-testing',
'igny8_help_page'
);
// Function Testing subpage under Help
add_submenu_page(
'igny8-help',
'Function Testing',
'Function Testing',
'manage_options',
'igny8-help&sp=function-testing',
'igny8_help_page'
);
}
// Static page wrapper functions - each page handles its own layout
function igny8_home_page() {
include_once plugin_dir_path(__FILE__) . '../../modules/home.php';
}
function igny8_planner_page() {
include_once plugin_dir_path(__FILE__) . '../../modules/planner/planner.php';
}
function igny8_writer_page() {
include_once plugin_dir_path(__FILE__) . '../../modules/writer/writer.php';
}
function igny8_thinker_page() {
include_once plugin_dir_path(__FILE__) . '../../modules/thinker/thinker.php';
}
function igny8_settings_page() {
include_once plugin_dir_path(__FILE__) . '../../modules/settings/general-settings.php';
}
function igny8_analytics_page() {
include_once plugin_dir_path(__FILE__) . '../../modules/analytics/analytics.php';
}
function igny8_schedules_page() {
include_once plugin_dir_path(__FILE__) . '../../modules/settings/schedules.php';
}
function igny8_help_page() {
include_once plugin_dir_path(__FILE__) . '../../modules/help/help.php';
}
// Hook into admin_menu
add_action('admin_menu', 'igny8_register_admin_menu');

View File

@@ -0,0 +1,387 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : meta-boxes.php
* @location : /core/admin/meta-boxes.php
* @type : Function Library
* @scope : Global
* @allowed : Meta box registration, SEO field management
* @reusability : Globally Reusable
* @notes : SEO meta boxes for post editor
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// === SEO Meta Box ===
add_action('add_meta_boxes', function() {
// SEO fields
add_meta_box('igny8_seo_meta', 'Igny8 SEO Fields', function($post) {
$meta_title = get_post_meta($post->ID, '_igny8_meta_title', true);
$meta_desc = get_post_meta($post->ID, '_igny8_meta_description', true);
$primary_kw = get_post_meta($post->ID, '_igny8_primary_keywords', true);
$secondary_kw = get_post_meta($post->ID, '_igny8_secondary_keywords', true);
?>
<div style="padding:8px 4px;">
<label><strong>Meta Title:</strong></label><br>
<input type="text" name="_igny8_meta_title" value="<?php echo esc_attr($meta_title); ?>" style="width:100%;"><br><br>
<label><strong>Meta Description:</strong></label><br>
<textarea name="_igny8_meta_description" rows="3" style="width:100%;"><?php echo esc_textarea($meta_desc); ?></textarea><br><br>
<label><strong>Primary Keyword:</strong></label><br>
<input type="text" name="_igny8_primary_keywords" value="<?php echo esc_attr($primary_kw); ?>" style="width:100%;"><br><br>
<label><strong>Secondary Keywords (comma-separated):</strong></label><br>
<input type="text" name="_igny8_secondary_keywords" value="<?php echo esc_attr($secondary_kw); ?>" style="width:100%;">
</div>
<?php
}, ['post','page','product'], 'normal', 'high');
});
// === Save Meta Box Data ===
add_action('save_post', function($post_id) {
// Security checks
if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) {
return;
}
// Save SEO fields
$fields = [
'_igny8_meta_title',
'_igny8_meta_description',
'_igny8_primary_keywords',
'_igny8_secondary_keywords',
];
foreach ($fields as $field) {
if (isset($_POST[$field])) {
update_post_meta($post_id, $field, sanitize_text_field($_POST[$field]));
}
}
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log("Igny8 Metabox: SEO fields saved for post $post_id");
}
});
// === In-Article Image Gallery Meta Box ===
add_action('add_meta_boxes', function() {
$enabled_post_types = get_option('igny8_enable_image_metabox', []);
foreach ((array) $enabled_post_types as $pt) {
add_meta_box(
'igny8_image_gallery',
'Igny8 In-Article Images',
'igny8_render_image_gallery_metabox',
$pt,
'side',
'high'
);
}
});
function igny8_render_image_gallery_metabox($post) {
wp_nonce_field('igny8_save_image_gallery', 'igny8_image_gallery_nonce');
$images = get_post_meta($post->ID, '_igny8_inarticle_images', true);
if (!is_array($images)) $images = [];
// Add CSS for grid layout and remove button
?>
<style>
.igny8-image-list {
display: flex;
flex-wrap: wrap;
list-style: none;
margin: 0;
padding: 0;
gap: 5px;
}
.igny8-image-list li {
width: 200px;
margin: 0 5px 5px 0;
box-sizing: border-box;
text-align: center;
position: relative;
border: 1px solid #eee;
padding: 5px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 180px;
}
.igny8-image-list li img {
display: block;
margin: 0 auto 5px auto;
}
.igny8-image-actions {
position: absolute;
bottom: 5px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 5px;
opacity: 0;
transition: opacity 0.2s ease;
z-index: 10;
}
.igny8-image-list li:hover .igny8-image-actions {
opacity: 1;
}
.igny8-remove-btn, .igny8-replace-btn {
background: #f0f0f0;
border: 1px solid #ccc;
color: #333;
font-size: 10px;
padding: 2px 6px;
cursor: pointer;
border-radius: 2px;
text-decoration: none;
white-space: nowrap;
}
.igny8-remove-btn:hover {
background: #dc3232;
color: #fff;
border-color: #dc3232;
}
.igny8-replace-btn:hover {
background: #0073aa;
color: #fff;
border-color: #0073aa;
}
.igny8-image-list li strong {
font-size: 0.8em;
word-break: break-all;
}
</style>
<?php
echo '<div id="igny8-image-gallery">';
echo '<p><label><input type="radio" name="igny8_image_type" value="desktop" checked> Desktop</label>
<label><input type="radio" name="igny8_image_type" value="mobile"> Mobile</label></p>';
echo '<button type="button" class="button button-primary" id="igny8-add-image">Add Image</button>';
echo '<ul class="igny8-image-list">';
// Sort images by device type and ID
$sorted_images = [];
foreach ($images as $label => $data) {
$sorted_images[$label] = $data;
}
// Custom sort function to order by device type (desktop first) then by ID
uksort($sorted_images, function($a, $b) {
$a_parts = explode('-', $a);
$b_parts = explode('-', $b);
$a_device = $a_parts[0];
$b_device = $b_parts[0];
// Desktop comes before mobile
if ($a_device === 'desktop' && $b_device === 'mobile') return -1;
if ($a_device === 'mobile' && $b_device === 'desktop') return 1;
// If same device, sort by ID number
if ($a_device === $b_device) {
$a_id = intval($a_parts[1]);
$b_id = intval($b_parts[1]);
return $a_id - $b_id;
}
return 0;
});
foreach ($sorted_images as $label => $data) {
echo '<li data-label="' . esc_attr($label) . '">';
echo '<strong>' . esc_html($label) . '</strong><br>';
echo wp_get_attachment_image($data['attachment_id'], 'thumbnail', false, ['style' => 'width: 150px; height: 150px; object-fit: cover;']);
echo '<div class="igny8-image-actions">';
echo '<button type="button" class="igny8-replace-btn">Replace</button>';
echo '<button type="button" class="igny8-remove-btn">Remove</button>';
echo '</div>';
echo '<input type="hidden" name="igny8_image_data[' . esc_attr($label) . '][attachment_id]" value="' . esc_attr($data['attachment_id']) . '">';
echo '<input type="hidden" name="igny8_image_data[' . esc_attr($label) . '][device]" value="' . esc_attr($data['device']) . '">';
echo '</li>';
}
echo '</ul>';
// Inline JS
?>
<script>
jQuery(document).ready(function($) {
// Function to get first available ID for a device type
function getFirstAvailableId(deviceType) {
let existingIds = [];
$('.igny8-image-list li').each(function() {
let label = $(this).data('label');
if (label && label.startsWith(deviceType + '-')) {
let id = parseInt(label.split('-')[1]);
if (!isNaN(id)) {
existingIds.push(id);
}
}
});
// Sort existing IDs and find first gap
existingIds.sort((a, b) => a - b);
for (let i = 1; i <= existingIds.length + 1; i++) {
if (!existingIds.includes(i)) {
return i;
}
}
return 1; // Fallback
}
$('#igny8-add-image').on('click', function(e) {
e.preventDefault();
let type = $('input[name="igny8_image_type"]:checked').val() || 'desktop';
let availableId = getFirstAvailableId(type);
let label = type + '-' + availableId;
const frame = wp.media({
title: 'Select Image',
button: { text: 'Use this image' },
multiple: false
});
frame.on('select', function() {
let attachment = frame.state().get('selection').first().toJSON();
let html = '<li data-label="' + label + '">' +
'<strong>' + label + '</strong><br>' +
'<img src="' + attachment.sizes.thumbnail.url + '" style="width: 150px; height: 150px; object-fit: cover;" /><br>' +
'<div class="igny8-image-actions">' +
'<button type="button" class="igny8-replace-btn">Replace</button>' +
'<button type="button" class="igny8-remove-btn">Remove</button>' +
'</div>' +
'<input type="hidden" name="igny8_image_data[' + label + '][attachment_id]" value="' + attachment.id + '">' +
'<input type="hidden" name="igny8_image_data[' + label + '][device]" value="' + type + '">' +
'</li>';
$('.igny8-image-list').append(html);
});
frame.open();
});
// Handle image removal (event delegation for dynamically added elements)
$(document).on('click', '.igny8-remove-btn', function() {
$(this).closest('li').remove();
});
// Handle image replacement (event delegation for dynamically added elements)
$(document).on('click', '.igny8-replace-btn', function() {
let $li = $(this).closest('li');
let label = $li.data('label');
let type = label.split('-')[0];
const frame = wp.media({
title: 'Replace Image',
button: { text: 'Replace this image' },
multiple: false
});
frame.on('select', function() {
let attachment = frame.state().get('selection').first().toJSON();
// Replace the entire img element to force reload
let $img = $li.find('img');
let newImg = $('<img>').attr({
'src': attachment.sizes.thumbnail.url,
'style': 'width: 150px; height: 150px; object-fit: cover;'
});
$img.replaceWith(newImg);
// Update the hidden input
$li.find('input[name*="[attachment_id]"]').val(attachment.id);
});
frame.open();
});
// Handle Convert Content to Blocks
$('#igny8-convert-to-blocks').on('click', function(e) {
e.preventDefault();
if (!confirm('This will convert the post content from HTML to WordPress blocks. Continue?')) {
return;
}
let $button = $(this);
let originalText = $button.text();
$button.prop('disabled', true).text('Converting...');
// Get post ID from the current post
let postId = $('#post_ID').val();
// Make AJAX request to convert content to blocks
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'igny8_convert_content_to_blocks',
post_id: postId,
nonce: '<?php echo wp_create_nonce('igny8_convert_to_blocks'); ?>'
},
success: function(response) {
console.log('Convert to Blocks Response:', response);
if (response.success) {
console.log('Success data:', response.data);
alert('Content converted successfully! ' + response.data.message);
location.reload();
} else {
console.error('Error data:', response.data);
alert('Error: ' + (response.data || 'Unknown error'));
}
},
error: function(xhr, status, error) {
console.error('Convert to Blocks Error:', {status, error, responseText: xhr.responseText});
alert('Error converting content. Check console for details.');
},
complete: function() {
$button.prop('disabled', false).text(originalText);
}
});
});
});
</script>
<?php
}
// SAVE HANDLER for Image Gallery
add_action('save_post', function($post_id) {
if (!isset($_POST['igny8_image_gallery_nonce']) || !wp_verify_nonce($_POST['igny8_image_gallery_nonce'], 'igny8_save_image_gallery')) return;
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (!current_user_can('edit_post', $post_id)) return;
$images = $_POST['igny8_image_data'] ?? [];
$filtered = [];
foreach ($images as $label => $data) {
if (!empty($data['attachment_id'])) {
$filtered[$label] = [
'label' => sanitize_text_field($label),
'attachment_id' => intval($data['attachment_id']),
'url' => wp_get_attachment_url(intval($data['attachment_id'])),
'device' => sanitize_text_field($data['device'])
];
}
}
update_post_meta($post_id, '_igny8_inarticle_images', $filtered);
if (WP_DEBUG === true) {
error_log("[IGNY8 DEBUG] Saving In-Article Images for Post ID: $post_id");
error_log(print_r($filtered, true));
}
});

View File

@@ -0,0 +1,181 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : module-manager-class.php
* @location : /core/admin/module-manager-class.php
* @type : Function Library
* @scope : Global
* @allowed : Module management, class definitions, core functionality
* @reusability : Globally Reusable
* @notes : Module manager class for core functionality
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Igny8 Module Manager - Controls which modules are active
*/
class Igny8_Module_Manager {
private static $instance = null;
private $modules = [];
public static function get_instance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->init_modules();
add_action('admin_init', [$this, 'register_module_settings']);
}
/**
* Initialize module definitions - main modules only
*/
private function init_modules() {
$this->modules = [
'planner' => [
'name' => 'Planner',
'description' => 'Keyword research and content planning with clusters, ideas, and mapping tools.',
'default' => true,
'icon' => 'dashicons-search',
'category' => 'main',
'cron_jobs' => [
'igny8_auto_cluster_cron',
'igny8_auto_generate_ideas_cron',
'igny8_auto_queue_cron'
]
],
'writer' => [
'name' => 'Writer',
'description' => 'AI-powered content generation with drafts and templates management.',
'default' => false,
'icon' => 'dashicons-edit',
'category' => 'main',
'cron_jobs' => [
'igny8_auto_generate_content_cron',
'igny8_auto_generate_images_cron',
'igny8_auto_publish_drafts_cron'
]
],
'analytics' => [
'name' => 'Analytics',
'description' => 'Performance tracking and data analysis with comprehensive reporting.',
'default' => false,
'icon' => 'dashicons-chart-bar',
'category' => 'admin',
'cron_jobs' => [
'igny8_process_ai_queue_cron',
'igny8_auto_recalc_cron',
'igny8_health_check_cron'
]
],
'schedules' => [
'name' => 'Schedules',
'description' => 'Content scheduling and automation with calendar management.',
'default' => false,
'icon' => 'dashicons-calendar-alt',
'category' => 'admin'
],
'thinker' => [
'name' => 'AI Thinker',
'description' => 'AI-powered content strategy, prompts, and intelligent content planning tools.',
'default' => true,
'icon' => 'dashicons-lightbulb',
'category' => 'admin'
]
];
}
/**
* Check if a module is enabled
*/
public function is_module_enabled($module) {
$settings = get_option('igny8_module_settings', []);
return isset($settings[$module]) ? (bool) $settings[$module] : (isset($this->modules[$module]) ? $this->modules[$module]['default'] : false);
}
/**
* Get all enabled modules
*/
public function get_enabled_modules() {
$enabled = [];
foreach ($this->modules as $key => $module) {
if ($this->is_module_enabled($key)) {
$enabled[$key] = $module;
}
}
return $enabled;
}
/**
* Get all modules
*/
public function get_modules() {
return $this->modules;
}
/**
* Register module settings
*/
public function register_module_settings() {
register_setting('igny8_module_settings', 'igny8_module_settings');
}
/**
* Save module settings
*/
public function save_module_settings() {
if (!isset($_POST['igny8_module_nonce']) || !wp_verify_nonce($_POST['igny8_module_nonce'], 'igny8_module_settings')) {
wp_die('Security check failed');
}
$settings = $_POST['igny8_module_settings'] ?? [];
// Initialize all modules as disabled first
$all_modules = $this->get_modules();
$final_settings = [];
foreach ($all_modules as $module_key => $module) {
$final_settings[$module_key] = false; // Default to disabled
}
// Set enabled modules to true
foreach ($settings as $key => $value) {
if (isset($final_settings[$key])) {
$final_settings[$key] = (bool) $value;
}
}
update_option('igny8_module_settings', $final_settings);
// Force page reload using JavaScript
echo '<script>window.location.reload();</script>';
exit;
}
}
// Initialize the module manager
function igny8_module_manager() {
return Igny8_Module_Manager::get_instance();
}
// Helper functions for easy access
function igny8_is_module_enabled($module) {
return igny8_module_manager()->is_module_enabled($module);
}
function igny8_get_enabled_modules() {
return igny8_module_manager()->get_enabled_modules();
}
function igny8_get_modules() {
return igny8_module_manager()->get_modules();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,384 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : igny8-cron-master-dispatcher.php
* @location : /core/cron/igny8-cron-master-dispatcher.php
* @type : CRON Handler
* @scope : Global
* @allowed : Cron scheduling, automation dispatch, resource management
* @reusability : Globally Reusable
* @notes : Central cron dispatcher for all automation jobs
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Master Dispatcher - Main execution function
*
* Runs every 5 minutes via cPanel, checks database schedules,
* and executes only due automations with proper limits and timing.
*/
function igny8_master_dispatcher_run() {
echo "<div style='background:#e8f4fd;padding:10px;margin:5px;border:1px solid #2196F3;'>";
echo "<strong>Igny8 MASTER DISPATCHER: Starting smart automation check</strong><br>";
error_log("Igny8 MASTER DISPATCHER: Starting smart automation check");
// Get all defined cron jobs
$cron_jobs = igny8_get_defined_cron_jobs();
$current_time = current_time('timestamp');
$executed_jobs = [];
$skipped_jobs = [];
echo "<strong>Igny8 MASTER DISPATCHER: Found " . count($cron_jobs) . " defined jobs</strong><br>";
// Get settings and limits
$cron_settings = get_option('igny8_cron_settings', []);
$cron_limits = get_option('igny8_cron_limits', []);
// Initialize default settings if missing
if (empty($cron_settings)) {
$cron_settings = igny8_get_default_cron_settings();
update_option('igny8_cron_settings', $cron_settings);
echo "<strong>Igny8 MASTER DISPATCHER: Initialized default settings</strong><br>";
}
if (empty($cron_limits)) {
$cron_limits = igny8_get_default_cron_limits();
update_option('igny8_cron_limits', $cron_limits);
echo "<strong>Igny8 MASTER DISPATCHER: Initialized default limits</strong><br>";
}
// Process each job in priority order
foreach ($cron_jobs as $job_name => $job_config) {
echo "<strong>Igny8 MASTER DISPATCHER: Checking job: " . $job_name . "</strong><br>";
// Check if job is enabled
$job_settings = $cron_settings[$job_name] ?? [];
if (!($job_settings['enabled'] ?? false)) {
echo "<strong>Igny8 MASTER DISPATCHER: Job disabled, skipping</strong><br>";
$skipped_jobs[] = $job_name;
continue;
}
// Check if job is due (simplified - just check if enabled and not recently run)
$last_run = $job_settings['last_run'] ?? 0;
$time_since_last_run = $current_time - $last_run;
// Run job if it hasn't been run in the last 5 minutes (to prevent duplicate runs)
if ($time_since_last_run < 300) {
echo "<strong>Igny8 MASTER DISPATCHER: Job run recently, skipping</strong><br>";
$skipped_jobs[] = $job_name;
continue;
}
// Check if job is already running (duplicate prevention)
$lock_key = 'igny8_cron_running_' . $job_name;
if (get_transient($lock_key)) {
echo "<strong>Igny8 MASTER DISPATCHER: Job already running, skipping</strong><br>";
$skipped_jobs[] = $job_name;
continue;
}
// Set lock for this job
$max_execution_time = $job_config['max_execution_time'] ?? 300;
set_transient($lock_key, true, $max_execution_time);
echo "<strong>Igny8 MASTER DISPATCHER: Executing job: " . $job_name . "</strong><br>";
try {
// Get job limit
$job_limit = $cron_limits[$job_name] ?? 1;
// Set limit as global variable for handlers to use
$GLOBALS['igny8_cron_limit'] = $job_limit;
// Execute the job
$start_time = microtime(true);
do_action($job_name);
$execution_time = microtime(true) - $start_time;
// Update last run time
$cron_settings[$job_name]['last_run'] = $current_time;
update_option('igny8_cron_settings', $cron_settings);
// Track individual job execution with detailed logging
$processed_count = $GLOBALS['igny8_cron_processed_count'] ?? 0;
$result_details = $GLOBALS['igny8_cron_result_details'] ?? '';
echo "<strong>Igny8 MASTER DISPATCHER: Global variables - processed_count: $processed_count, result_details: $result_details</strong><br>";
$job_health = [
'last_run' => $current_time,
'success' => true,
'last_success' => true,
'execution_time' => round($execution_time, 2),
'error_message' => '',
'processed_count' => $processed_count,
'result_details' => $result_details,
'execution_method' => (isset($_GET['import_key']) && !empty($_GET['import_key'])) ? 'external_url' : 'server_cron'
];
update_option('igny8_cron_health_' . $job_name, $job_health);
$executed_jobs[] = [
'job' => $job_name,
'execution_time' => round($execution_time, 2),
'success' => true
];
echo "<strong>Igny8 MASTER DISPATCHER: Job completed successfully in " . round($execution_time, 2) . "s</strong><br>";
} catch (Exception $e) {
echo "<strong>Igny8 MASTER DISPATCHER: Job failed: " . $e->getMessage() . "</strong><br>";
error_log("Igny8 MASTER DISPATCHER: Job $job_name failed - " . $e->getMessage());
// Track individual job failure
$job_health = [
'last_run' => $current_time,
'success' => false,
'last_success' => false,
'execution_time' => 0,
'error_message' => $e->getMessage(),
'processed_count' => $GLOBALS['igny8_cron_processed_count'] ?? 0,
'result_details' => 'FAILED: ' . $e->getMessage(),
'execution_method' => (isset($_GET['import_key']) && !empty($_GET['import_key'])) ? 'external_url' : 'server_cron'
];
update_option('igny8_cron_health_' . $job_name, $job_health);
$executed_jobs[] = [
'job' => $job_name,
'execution_time' => 0,
'success' => false,
'last_success' => false,
'error' => $e->getMessage()
];
} catch (Throwable $e) {
echo "<strong>Igny8 MASTER DISPATCHER: Job fatal error: " . $e->getMessage() . "</strong><br>";
error_log("Igny8 MASTER DISPATCHER: Job $job_name fatal error - " . $e->getMessage());
// Track individual job failure
$job_health = [
'last_run' => $current_time,
'success' => false,
'last_success' => false,
'execution_time' => 0,
'error_message' => $e->getMessage(),
'processed_count' => $GLOBALS['igny8_cron_processed_count'] ?? 0,
'result_details' => 'FAILED: ' . $e->getMessage(),
'execution_method' => (isset($_GET['import_key']) && !empty($_GET['import_key'])) ? 'external_url' : 'server_cron'
];
update_option('igny8_cron_health_' . $job_name, $job_health);
$executed_jobs[] = [
'job' => $job_name,
'execution_time' => 0,
'success' => false,
'last_success' => false,
'error' => $e->getMessage()
];
} finally {
// Always release the lock
delete_transient($lock_key);
}
}
// Log summary
echo "<strong>Igny8 MASTER DISPATCHER: Execution summary</strong><br>";
echo "<strong>Igny8 MASTER DISPATCHER: Jobs executed: " . count($executed_jobs) . "</strong><br>";
echo "<strong>Igny8 MASTER DISPATCHER: Jobs skipped: " . count($skipped_jobs) . "</strong><br>";
// Store execution log
update_option('igny8_cron_last_execution', [
'timestamp' => $current_time,
'executed' => $executed_jobs,
'skipped' => $skipped_jobs
]);
echo "<strong>Igny8 MASTER DISPATCHER: Smart automation check completed</strong><br>";
echo "</div>";
// Return success response for external cron
return [
'success' => true,
'message' => 'Master dispatcher executed successfully',
'executed' => count($executed_jobs),
'skipped' => count($skipped_jobs),
'timestamp' => current_time('mysql')
];
}
/**
* Get all defined cron jobs with their configurations
*/
function igny8_get_defined_cron_jobs() {
return [
'igny8_auto_cluster_cron' => [
'handler' => 'igny8_auto_cluster_cron_handler',
'priority' => 1,
'max_execution_time' => 600, // 10 minutes
'description' => 'Auto cluster unmapped keywords',
'module' => 'planner'
],
'igny8_auto_generate_ideas_cron' => [
'handler' => 'igny8_auto_generate_ideas_cron_handler',
'priority' => 2,
'max_execution_time' => 300, // 5 minutes
'description' => 'Auto generate ideas from clusters',
'module' => 'planner'
],
'igny8_auto_queue_cron' => [
'handler' => 'igny8_auto_queue_cron_handler',
'priority' => 3,
'max_execution_time' => 300, // 5 minutes
'description' => 'Auto queue new ideas',
'module' => 'planner'
],
'igny8_auto_generate_content_cron' => [
'handler' => 'igny8_auto_generate_content_cron_handler',
'priority' => 4,
'max_execution_time' => 600, // 10 minutes
'description' => 'Auto generate content from queued tasks',
'module' => 'writer'
],
'igny8_auto_generate_images_cron' => [
'handler' => 'igny8_auto_generate_images_cron_handler',
'priority' => 5,
'max_execution_time' => 900, // 15 minutes
'description' => 'Auto generate images for content',
'module' => 'writer'
],
'igny8_auto_publish_drafts_cron' => [
'handler' => 'igny8_auto_publish_drafts_cron_handler',
'priority' => 6,
'max_execution_time' => 300, // 5 minutes
'description' => 'Auto publish completed drafts',
'module' => 'writer'
],
'igny8_process_ai_queue_cron' => [
'handler' => 'igny8_process_ai_queue_cron_handler',
'priority' => 7,
'max_execution_time' => 300, // 5 minutes
'description' => 'Process AI queue tasks',
'module' => 'ai'
],
'igny8_auto_recalc_cron' => [
'handler' => 'igny8_auto_recalc_cron_handler',
'priority' => 8,
'max_execution_time' => 300, // 5 minutes
'description' => 'Auto recalculate metrics',
'module' => 'analytics'
],
'igny8_auto_optimizer_cron' => [
'handler' => 'igny8_auto_optimizer_cron_handler',
'priority' => 9,
'max_execution_time' => 300, // 5 minutes
'description' => 'Auto optimize content and keywords',
'module' => 'optimizer'
],
'igny8_health_check_cron' => [
'handler' => 'igny8_health_check_cron_handler',
'priority' => 10,
'max_execution_time' => 300, // 5 minutes
'description' => 'System health check and cleanup',
'module' => 'system'
]
];
}
/**
* Get default cron settings
*/
function igny8_get_default_cron_settings() {
$jobs = igny8_get_defined_cron_jobs();
$settings = [];
foreach ($jobs as $job_name => $config) {
$settings[$job_name] = [
'enabled' => false, // Default to disabled
'last_run' => 0
];
}
return $settings;
}
/**
* Get default cron limits
*/
function igny8_get_default_cron_limits() {
return [
'igny8_auto_cluster_cron' => 1,
'igny8_auto_generate_ideas_cron' => 1,
'igny8_auto_queue_cron' => 1,
'igny8_auto_generate_content_cron' => 1,
'igny8_auto_generate_images_cron' => 1,
'igny8_auto_publish_drafts_cron' => 1,
'igny8_process_ai_queue_cron' => 1,
'igny8_auto_recalc_cron' => 1,
'igny8_auto_optimizer_cron' => 1,
'igny8_health_check_cron' => 1
];
}
/**
* Update cron settings for a specific job
*/
function igny8_update_cron_job_settings($job_name, $settings) {
$cron_settings = get_option('igny8_cron_settings', []);
$cron_settings[$job_name] = array_merge($cron_settings[$job_name] ?? [], $settings);
update_option('igny8_cron_settings', $cron_settings);
}
/**
* Update cron limits for a specific job
*/
function igny8_update_cron_job_limits($job_name, $limit) {
$cron_limits = get_option('igny8_cron_limits', []);
$cron_limits[$job_name] = $limit;
update_option('igny8_cron_limits', $cron_limits);
}
/**
* Get health status for a specific job
*/
function igny8_get_job_health_status($job_name) {
$health = get_option('igny8_cron_health_' . $job_name, []);
$cron_settings = get_option('igny8_cron_settings', []);
$job_settings = $cron_settings[$job_name] ?? [];
return [
'enabled' => $job_settings['enabled'] ?? false,
'last_run' => isset($health['last_run']) ? date('Y-m-d H:i:s', $health['last_run']) : 'Never',
'last_success' => $health['success'] ?? null,
'execution_time' => isset($health['execution_time']) ? round($health['execution_time'], 2) : 0,
'error_message' => $health['error_message'] ?? '',
'processed_count' => $health['processed_count'] ?? 0,
'result_details' => $health['result_details'] ?? '',
'execution_method' => $health['execution_method'] ?? 'unknown'
];
}
/**
* Get cron job status and next run time
*/
function igny8_get_cron_job_status($job_name) {
$cron_settings = get_option('igny8_cron_settings', []);
$job_settings = $cron_settings[$job_name] ?? [];
if (empty($job_settings)) {
return [
'enabled' => false,
'last_run' => 'Never'
];
}
return [
'enabled' => $job_settings['enabled'] ?? false,
'last_run' => isset($job_settings['last_run']) && $job_settings['last_run'] ? date('Y-m-d H:i:s', $job_settings['last_run']) : 'Never'
];
}

View File

@@ -0,0 +1,253 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : db-migration.php
* @location : /core/db/db-migration.php
* @type : Function Library
* @scope : Global
* @allowed : Database migrations, schema updates, version tracking
* @reusability : Globally Reusable
* @notes : Database migration system for schema updates
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* ========================================================================
* MIGRATION SYSTEM TEMPLATE
* ========================================================================
*/
/**
* Get current database version
*/
function igny8_get_db_version() {
return get_option('igny8_db_version', '0.1');
}
/**
* Set database version
*/
function igny8_set_db_version($version) {
update_option('igny8_db_version', $version);
}
/**
* Check if migration is needed
*/
function igny8_is_migration_needed($target_version = null) {
if (!$target_version) {
$target_version = '0.1'; // Current version
}
$current_version = igny8_get_db_version();
return version_compare($current_version, $target_version, '<');
}
/**
* Run all pending migrations
*/
function igny8_run_migrations() {
$current_version = igny8_get_db_version();
$target_version = '0.1';
if (!igny8_is_migration_needed($target_version)) {
return true; // No migration needed
}
// Example migration structure:
// if (version_compare($current_version, '2.7.0', '<')) {
// igny8_migration_270();
// }
// if (version_compare($current_version, '2.7.1', '<')) {
// igny8_migration_271();
// }
// Update to latest version
igny8_set_db_version($target_version);
return true;
}
/**
* ========================================================================
* MIGRATION FUNCTIONS TEMPLATE
* ========================================================================
*/
/**
* Example migration function template
*
* @param string $from_version Version migrating from
* @param string $to_version Version migrating to
*/
function igny8_migration_template($from_version, $to_version) {
global $wpdb;
try {
// Example: Add new column
// $table_name = $wpdb->prefix . 'igny8_example_table';
// $column_exists = $wpdb->get_var("SHOW COLUMNS FROM `$table_name` LIKE 'new_column'");
// if (!$column_exists) {
// $wpdb->query("ALTER TABLE `$table_name` ADD COLUMN `new_column` VARCHAR(255) DEFAULT NULL");
// }
// Example: Create new table
// $sql = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}igny8_new_table (
// id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
// name VARCHAR(255) NOT NULL,
// created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
// PRIMARY KEY (id)
// ) {$wpdb->get_charset_collate()};";
// dbDelta($sql);
// Example: Migrate data
// $wpdb->query("UPDATE {$wpdb->prefix}igny8_table SET old_field = new_field WHERE condition");
error_log("Igny8 Migration: Successfully migrated from $from_version to $to_version");
return true;
} catch (Exception $e) {
error_log("Igny8 Migration Error: " . $e->getMessage());
return false;
}
}
/**
* ========================================================================
* MIGRATION UTILITIES
* ========================================================================
*/
/**
* Backup table before migration
*/
function igny8_backup_table($table_name, $suffix = null) {
global $wpdb;
if (!$suffix) {
$suffix = '_backup_' . date('Y_m_d_H_i_s');
}
$backup_table = $table_name . $suffix;
try {
$wpdb->query("CREATE TABLE `$backup_table` LIKE `$table_name`");
$wpdb->query("INSERT INTO `$backup_table` SELECT * FROM `$table_name`");
return $backup_table;
} catch (Exception $e) {
error_log("Igny8 Migration: Failed to backup table $table_name - " . $e->getMessage());
return false;
}
}
/**
* Restore table from backup
*/
function igny8_restore_table($table_name, $backup_table) {
global $wpdb;
try {
$wpdb->query("DROP TABLE IF EXISTS `$table_name`");
$wpdb->query("CREATE TABLE `$table_name` LIKE `$backup_table`");
$wpdb->query("INSERT INTO `$table_name` SELECT * FROM `$backup_table`");
return true;
} catch (Exception $e) {
error_log("Igny8 Migration: Failed to restore table $table_name - " . $e->getMessage());
return false;
}
}
/**
* Check if table exists
*/
function igny8_table_exists($table_name) {
global $wpdb;
return $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
}
/**
* Check if column exists in table
*/
function igny8_column_exists($table_name, $column_name) {
global $wpdb;
$result = $wpdb->get_var("SHOW COLUMNS FROM `$table_name` LIKE '$column_name'");
return !empty($result);
}
/**
* Get table structure
*/
function igny8_get_table_structure($table_name) {
global $wpdb;
return $wpdb->get_results("DESCRIBE `$table_name`", ARRAY_A);
}
/**
* ========================================================================
* AUTO-MIGRATION ON PLUGIN UPDATE
* ========================================================================
*/
/**
* Auto-run migrations on plugin update
*/
function igny8_auto_run_migrations() {
if (current_user_can('manage_options') && igny8_is_migration_needed()) {
igny8_run_migrations();
}
}
// Hook to auto-run migrations on admin_init
add_action('admin_init', 'igny8_auto_run_migrations');
/**
* ========================================================================
* MIGRATION STATUS & LOGGING
* ========================================================================
*/
/**
* Log migration event
*/
function igny8_log_migration($from_version, $to_version, $status = 'success', $message = '') {
$log_entry = [
'timestamp' => current_time('mysql'),
'from_version' => $from_version,
'to_version' => $to_version,
'status' => $status,
'message' => $message,
'user_id' => get_current_user_id()
];
// Store in options (you could also use the logs table)
$migration_logs = get_option('igny8_migration_logs', []);
$migration_logs[] = $log_entry;
// Keep only last 50 migration logs
if (count($migration_logs) > 50) {
$migration_logs = array_slice($migration_logs, -50);
}
update_option('igny8_migration_logs', $migration_logs);
}
/**
* Get migration logs
*/
function igny8_get_migration_logs($limit = 10) {
$logs = get_option('igny8_migration_logs', []);
return array_slice(array_reverse($logs), 0, $limit);
}
/**
* Clear migration logs
*/
function igny8_clear_migration_logs() {
delete_option('igny8_migration_logs');
}

View File

@@ -0,0 +1,970 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : db.php
* @location : /core/db/db.php
* @type : Function Library
* @scope : Global
* @allowed : Database operations, schema management, data queries
* @reusability : Globally Reusable
* @notes : Central database operations and schema management
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Clean up legacy database structures (if any exist from old installations)
*
* This function handles cleanup of any legacy structures that might exist
* from previous plugin versions, but all new installations use the correct schema.
*/
function igny8_cleanup_legacy_structures() {
global $wpdb;
$table_name = $wpdb->prefix . 'igny8_content_ideas';
// Only run cleanup if table exists
if (!$wpdb->get_var("SHOW TABLES LIKE '$table_name'")) {
return true;
}
try {
// Remove legacy priority column if it exists (from very old versions)
$priority_exists = $wpdb->get_var("SHOW COLUMNS FROM `$table_name` LIKE 'priority'");
if ($priority_exists) {
// Remove index first if it exists
$index_exists = $wpdb->get_var("SHOW INDEX FROM `$table_name` WHERE Key_name = 'idx_priority'");
if ($index_exists) {
$wpdb->query("ALTER TABLE `$table_name` DROP INDEX `idx_priority`");
}
// Drop the column
$wpdb->query("ALTER TABLE `$table_name` DROP COLUMN `priority`");
error_log('Igny8 Cleanup: Removed legacy priority column');
}
// Remove legacy ai_generated column if it exists (should be source now)
$ai_generated_exists = $wpdb->get_var("SHOW COLUMNS FROM `$table_name` LIKE 'ai_generated'");
if ($ai_generated_exists) {
// Check if source column exists
$source_exists = $wpdb->get_var("SHOW COLUMNS FROM `$table_name` LIKE 'source'");
if (!$source_exists) {
// Migrate data from ai_generated to source before dropping
$wpdb->query("ALTER TABLE `$table_name` ADD COLUMN `source` ENUM('AI','Manual') DEFAULT 'Manual'");
$wpdb->query("UPDATE `$table_name` SET source = CASE WHEN ai_generated = 1 THEN 'AI' ELSE 'Manual' END");
error_log('Igny8 Cleanup: Migrated ai_generated to source field');
}
// Drop the old ai_generated column
$wpdb->query("ALTER TABLE `$table_name` DROP COLUMN `ai_generated`");
error_log('Igny8 Cleanup: Removed legacy ai_generated column');
}
// Update any old status values to new format
$wpdb->query("UPDATE `$table_name` SET status = 'new' WHERE status NOT IN ('new','scheduled','published')");
return true;
} catch (Exception $e) {
error_log('Igny8 Cleanup Error: ' . $e->getMessage());
return false;
}
}
/**
* Check if legacy cleanup is needed
*/
function igny8_is_legacy_cleanup_needed() {
global $wpdb;
$table_name = $wpdb->prefix . 'igny8_content_ideas';
// Check if table exists
if (!$wpdb->get_var("SHOW TABLES LIKE '$table_name'")) {
return false;
}
// Check for legacy columns
$priority_exists = $wpdb->get_var("SHOW COLUMNS FROM `$table_name` LIKE 'priority'");
$ai_generated_exists = $wpdb->get_var("SHOW COLUMNS FROM `$table_name` LIKE 'ai_generated'");
return $priority_exists || $ai_generated_exists;
}
/**
* Auto-run legacy cleanup on admin_init if needed
*/
function igny8_auto_run_legacy_cleanup() {
if (current_user_can('manage_options') && igny8_is_legacy_cleanup_needed()) {
igny8_cleanup_legacy_structures();
}
}
// Hook to auto-run legacy cleanup (only for existing installations)
add_action('admin_init', 'igny8_auto_run_legacy_cleanup');
/**
* Auto-run logs table migration on admin_init if needed
*/
function igny8_auto_run_logs_migration() {
if (current_user_can('manage_options')) {
igny8_migrate_logs_table();
}
}
// Hook to auto-run logs migration (only for existing installations)
add_action('admin_init', 'igny8_auto_run_logs_migration');
/**
* Remove old migration option on plugin activation
*/
function igny8_cleanup_migration_options() {
delete_option('igny8_migration_ideas_schema_updated');
}
/**
* ========================================================================
* COMPLETE DATABASE SCHEMA CREATION
* ========================================================================
*/
/**
* Create all Igny8 database tables (15 tables total)
*/
function igny8_create_all_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// Keywords table
$sql = "CREATE TABLE {$wpdb->prefix}igny8_keywords (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
keyword VARCHAR(255) NOT NULL,
search_volume INT UNSIGNED DEFAULT 0,
difficulty INT UNSIGNED DEFAULT 0,
cpc FLOAT DEFAULT 0.00,
intent VARCHAR(50) DEFAULT 'informational',
cluster_id BIGINT UNSIGNED DEFAULT NULL,
sector_id BIGINT UNSIGNED DEFAULT NULL,
mapped_post_id BIGINT UNSIGNED DEFAULT NULL,
status ENUM('unmapped','mapped','queued','published') DEFAULT 'unmapped',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY unique_keyword (keyword),
KEY idx_cluster_id (cluster_id),
KEY idx_sector_id (sector_id),
KEY idx_mapped_post_id (mapped_post_id),
KEY idx_status (status),
KEY idx_created_at (created_at)
) $charset_collate;";
dbDelta($sql);
// Tasks table
$sql = "CREATE TABLE {$wpdb->prefix}igny8_tasks (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
description TEXT DEFAULT NULL,
status ENUM('pending','in_progress','completed','cancelled','draft','queued','review','published') DEFAULT 'pending',
priority ENUM('low','medium','high','urgent') DEFAULT 'medium',
due_date DATETIME DEFAULT NULL,
content_structure ENUM('cluster_hub','landing_page','guide_tutorial','how_to','comparison','review','top_listicle','question','product_description','service_page','home_page') DEFAULT 'cluster_hub',
content_type ENUM('post','product','page','CPT') DEFAULT 'post',
cluster_id BIGINT UNSIGNED DEFAULT NULL,
keywords TEXT DEFAULT NULL,
meta_title VARCHAR(255) DEFAULT NULL,
meta_description TEXT DEFAULT NULL,
word_count INT UNSIGNED DEFAULT 0,
raw_ai_response LONGTEXT DEFAULT NULL,
schedule_at DATETIME DEFAULT NULL,
assigned_post_id BIGINT UNSIGNED DEFAULT NULL,
idea_id BIGINT UNSIGNED DEFAULT NULL,
ai_writer ENUM('ai','human') DEFAULT 'ai',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_content_structure (content_structure),
KEY idx_content_type (content_type),
KEY idx_cluster_id (cluster_id),
KEY idx_status (status),
KEY idx_priority (priority),
KEY idx_assigned_post_id (assigned_post_id),
KEY idx_schedule_at (schedule_at),
KEY idx_idea_id (idea_id),
KEY idx_ai_writer (ai_writer),
KEY idx_created_at (created_at)
) $charset_collate;";
dbDelta($sql);
// Data table for personalization
$sql = "CREATE TABLE {$wpdb->prefix}igny8_data (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
post_id BIGINT UNSIGNED NOT NULL,
data_type VARCHAR(50) NOT NULL,
data JSON NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_post_id (post_id),
KEY idx_data_type (data_type),
KEY idx_created_at (created_at)
) $charset_collate;";
dbDelta($sql);
// Personalization variations table - stores AI-generated personalized content
$sql = "CREATE TABLE {$wpdb->prefix}igny8_variations (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
post_id BIGINT UNSIGNED NOT NULL,
fields_hash CHAR(64) NOT NULL,
fields_json LONGTEXT NOT NULL,
content LONGTEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_post_id (post_id),
KEY idx_fields_hash (fields_hash),
KEY idx_created_at (created_at),
UNIQUE KEY unique_variation (post_id, fields_hash)
) $charset_collate;";
dbDelta($sql);
// Rankings table
$sql = "CREATE TABLE {$wpdb->prefix}igny8_rankings (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
post_id BIGINT UNSIGNED NOT NULL,
keyword VARCHAR(255) NOT NULL,
impressions INT UNSIGNED DEFAULT 0,
clicks INT UNSIGNED DEFAULT 0,
ctr FLOAT DEFAULT 0.00,
avg_position FLOAT DEFAULT NULL,
source ENUM('gsc','ahrefs','manual') DEFAULT 'manual',
fetched_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_post_id (post_id),
KEY idx_keyword (keyword),
KEY idx_source (source),
KEY idx_fetched_at (fetched_at)
) $charset_collate;";
dbDelta($sql);
// Suggestions table
$sql = "CREATE TABLE {$wpdb->prefix}igny8_suggestions (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
post_id BIGINT UNSIGNED NOT NULL,
cluster_id BIGINT UNSIGNED DEFAULT NULL,
suggestion_type ENUM('internal_link','keyword_injection','rewrite') NOT NULL,
payload JSON DEFAULT NULL,
status ENUM('pending','applied','rejected') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
applied_at DATETIME DEFAULT NULL,
PRIMARY KEY (id),
KEY idx_post_id (post_id),
KEY idx_cluster_id (cluster_id),
KEY idx_suggestion_type (suggestion_type),
KEY idx_status (status),
KEY idx_created_at (created_at)
) $charset_collate;";
dbDelta($sql);
// Campaigns table
$sql = "CREATE TABLE {$wpdb->prefix}igny8_campaigns (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
cluster_id BIGINT UNSIGNED DEFAULT NULL,
target_post_id BIGINT UNSIGNED DEFAULT NULL,
name VARCHAR(255) NOT NULL,
status ENUM('active','completed','paused') DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_cluster_id (cluster_id),
KEY idx_target_post_id (target_post_id),
KEY idx_status (status),
KEY idx_created_at (created_at)
) $charset_collate;";
dbDelta($sql);
// Content Ideas table
$sql = "CREATE TABLE {$wpdb->prefix}igny8_content_ideas (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
idea_title VARCHAR(255) NOT NULL,
idea_description LONGTEXT DEFAULT NULL,
content_structure ENUM('cluster_hub','landing_page','guide_tutorial','how_to','comparison','review','top_listicle','question','product_description','service_page','home_page') DEFAULT 'cluster_hub',
content_type ENUM('post','product','page','CPT') DEFAULT 'post',
keyword_cluster_id BIGINT UNSIGNED DEFAULT NULL,
status ENUM('new','scheduled','published') DEFAULT 'new',
estimated_word_count INT UNSIGNED DEFAULT 0,
target_keywords TEXT DEFAULT NULL,
image_prompts TEXT DEFAULT NULL,
source ENUM('AI','Manual') DEFAULT 'Manual',
mapped_post_id BIGINT UNSIGNED DEFAULT NULL,
tasks_count INT UNSIGNED DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_idea_title (idea_title),
KEY idx_content_structure (content_structure),
KEY idx_content_type (content_type),
KEY idx_status (status),
KEY idx_keyword_cluster_id (keyword_cluster_id),
KEY idx_mapped_post_id (mapped_post_id),
KEY idx_created_at (created_at)
) $charset_collate;";
dbDelta($sql);
// Clusters table
$sql = "CREATE TABLE {$wpdb->prefix}igny8_clusters (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
cluster_name VARCHAR(255) NOT NULL,
sector_id BIGINT UNSIGNED DEFAULT NULL,
cluster_term_id BIGINT UNSIGNED DEFAULT NULL,
status ENUM('active','inactive','archived') DEFAULT 'active',
keyword_count INT UNSIGNED DEFAULT 0,
total_volume INT UNSIGNED DEFAULT 0,
avg_difficulty DECIMAL(5,2) DEFAULT 0.00,
mapped_pages_count INT UNSIGNED DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_cluster_name (cluster_name),
KEY idx_sector_id (sector_id),
KEY idx_cluster_term_id (cluster_term_id),
KEY idx_status (status),
KEY idx_created_at (created_at)
) $charset_collate;";
dbDelta($sql);
// Sites table
$sql = "CREATE TABLE {$wpdb->prefix}igny8_sites (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
site_url VARCHAR(500) NOT NULL,
site_name VARCHAR(255) DEFAULT NULL,
domain_authority INT UNSIGNED DEFAULT 0,
referring_domains INT UNSIGNED DEFAULT 0,
organic_traffic INT UNSIGNED DEFAULT 0,
status ENUM('active','inactive','blocked') DEFAULT 'active',
last_crawled DATETIME DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY unique_site_url (site_url),
KEY idx_domain_authority (domain_authority),
KEY idx_status (status),
KEY idx_last_crawled (last_crawled),
KEY idx_created_at (created_at)
) $charset_collate;";
dbDelta($sql);
// Backlinks table
$sql = "CREATE TABLE {$wpdb->prefix}igny8_backlinks (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
source_url VARCHAR(500) NOT NULL,
target_url VARCHAR(500) NOT NULL,
anchor_text VARCHAR(255) DEFAULT NULL,
link_type ENUM('dofollow','nofollow','sponsored','ugc') DEFAULT 'dofollow',
domain_authority INT UNSIGNED DEFAULT 0,
page_authority INT UNSIGNED DEFAULT 0,
status ENUM('pending','live','lost','disavowed') DEFAULT 'pending',
campaign_id BIGINT UNSIGNED DEFAULT NULL,
discovered_date DATE DEFAULT NULL,
lost_date DATE DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_source_url (source_url(191)),
KEY idx_target_url (target_url(191)),
KEY idx_link_type (link_type),
KEY idx_status (status),
KEY idx_campaign_id (campaign_id),
KEY idx_domain_authority (domain_authority),
KEY idx_discovered_date (discovered_date),
KEY idx_created_at (created_at)
) $charset_collate;";
dbDelta($sql);
// Mapping table
$sql = "CREATE TABLE {$wpdb->prefix}igny8_mapping (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
source_type ENUM('keyword','cluster','idea','task') NOT NULL,
source_id BIGINT UNSIGNED NOT NULL,
target_type ENUM('post','page','product') NOT NULL,
target_id BIGINT UNSIGNED NOT NULL,
mapping_type ENUM('primary','secondary','related') DEFAULT 'primary',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_source_type_id (source_type, source_id),
KEY idx_target_type_id (target_type, target_id),
KEY idx_mapping_type (mapping_type),
KEY idx_created_at (created_at)
) $charset_collate;";
dbDelta($sql);
// Prompts table
$sql = "CREATE TABLE {$wpdb->prefix}igny8_prompts (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
prompt_name VARCHAR(255) NOT NULL,
prompt_type ENUM('content','optimization','generation','custom') DEFAULT 'content',
prompt_text LONGTEXT NOT NULL,
variables JSON DEFAULT NULL,
is_active TINYINT(1) DEFAULT 1,
usage_count INT UNSIGNED DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY unique_prompt_name (prompt_name),
KEY idx_prompt_type (prompt_type),
KEY idx_is_active (is_active),
KEY idx_created_at (created_at)
) $charset_collate;";
dbDelta($sql);
// Logs table
$sql = "CREATE TABLE {$wpdb->prefix}igny8_logs (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
event_type VARCHAR(191) NOT NULL,
message TEXT NOT NULL,
context LONGTEXT NULL,
api_id VARCHAR(255) NULL,
status VARCHAR(50) NULL,
level VARCHAR(50) NULL,
source VARCHAR(100) NULL,
user_id BIGINT UNSIGNED NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_event_type (event_type),
KEY idx_created_at (created_at),
KEY idx_source (source),
KEY idx_status (status),
KEY idx_user_id (user_id)
) $charset_collate;";
dbDelta($sql);
// AI Queue table
$sql = "CREATE TABLE {$wpdb->prefix}igny8_ai_queue (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
action VARCHAR(50) NOT NULL,
data LONGTEXT NOT NULL,
user_id BIGINT UNSIGNED NOT NULL,
status ENUM('pending','processing','completed','failed') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
processed_at TIMESTAMP NULL,
result LONGTEXT NULL,
error_message TEXT NULL,
PRIMARY KEY (id),
KEY idx_user_id (user_id),
KEY idx_status (status),
KEY idx_action (action),
KEY idx_created_at (created_at)
) $charset_collate;";
dbDelta($sql);
// Update database version
update_option('igny8_db_version', '0.1');
}
/**
* Register Igny8 taxonomies with WordPress
*/
function igny8_register_taxonomies() {
// Register sectors taxonomy (hierarchical) - only if not exists
if (!taxonomy_exists('sectors')) {
register_taxonomy('sectors', ['post', 'page', 'product'], [
'hierarchical' => true,
'labels' => [
'name' => 'Sectors',
'singular_name' => 'Sector',
'menu_name' => 'Sectors',
'all_items' => 'All Sectors',
'edit_item' => 'Edit Sector',
'view_item' => 'View Sector',
'update_item' => 'Update Sector',
'add_new_item' => 'Add New Sector',
'new_item_name' => 'New Sector Name',
'parent_item' => 'Parent Sector',
'parent_item_colon' => 'Parent Sector:',
'search_items' => 'Search Sectors',
'popular_items' => 'Popular Sectors',
'separate_items_with_commas' => 'Separate sectors with commas',
'add_or_remove_items' => 'Add or remove sectors',
'choose_from_most_used' => 'Choose from most used sectors',
'not_found' => 'No sectors found',
],
'public' => true,
'show_ui' => true,
'show_admin_column' => true,
'show_in_nav_menus' => true,
'show_tagcloud' => true,
'show_in_rest' => true,
'rewrite' => [
'slug' => 'sectors',
'with_front' => false,
],
'capabilities' => [
'manage_terms' => 'manage_categories',
'edit_terms' => 'manage_categories',
'delete_terms' => 'manage_categories',
'assign_terms' => 'edit_posts',
],
]);
}
// Register clusters taxonomy (hierarchical) - only if not exists
if (!taxonomy_exists('clusters')) {
register_taxonomy('clusters', ['post', 'page', 'product'], [
'hierarchical' => true,
'labels' => [
'name' => 'Content Clusters',
'singular_name' => 'Cluster',
'menu_name' => 'Clusters',
'all_items' => 'All Clusters',
'edit_item' => 'Edit Cluster',
'view_item' => 'View Cluster',
'update_item' => 'Update Cluster',
'add_new_item' => 'Add New Cluster',
'new_item_name' => 'New Cluster Name',
'parent_item' => 'Parent Cluster',
'parent_item_colon' => 'Parent Cluster:',
'search_items' => 'Search Clusters',
'popular_items' => 'Popular Clusters',
'separate_items_with_commas' => 'Separate clusters with commas',
'add_or_remove_items' => 'Add or remove clusters',
'choose_from_most_used' => 'Choose from most used clusters',
'not_found' => 'No clusters found',
],
'public' => true,
'show_ui' => true,
'show_admin_column' => true,
'show_in_nav_menus' => true,
'show_tagcloud' => true,
'show_in_rest' => true,
'rewrite' => [
'slug' => 'clusters',
'with_front' => false,
],
'capabilities' => [
'manage_terms' => 'manage_categories',
'edit_terms' => 'manage_categories',
'delete_terms' => 'manage_categories',
'assign_terms' => 'edit_posts',
],
]);
}
}
// ==========================================================
// SEO: Prevent indexing of Cluster and Sector taxonomy pages
// ==========================================================
add_action('wp_head', function() {
if (is_tax(['clusters', 'sectors'])) {
echo '<meta name="robots" content="noindex,follow" />' . "\n";
}
}, 1);
/**
* Register Igny8 post meta fields with WordPress
*/
function igny8_register_post_meta() {
$post_types = ['post', 'page', 'product'];
// Define all meta fields with proper schema for REST API
$meta_fields = [
'_igny8_cluster_id' => [
'type' => 'integer',
'description' => 'Assigns content to a cluster',
'single' => true,
'show_in_rest'=> true,
],
'_igny8_keyword_ids' => [
'type' => 'array',
'description' => 'Maps multiple keywords to content',
'single' => true,
'show_in_rest'=> [
'schema' => [
'type' => 'array',
'items' => [
'type' => 'integer'
]
]
]
],
'_igny8_task_id' => [
'type' => 'integer',
'description' => 'Links WP content back to Writer task',
'single' => true,
'show_in_rest'=> true,
],
'_igny8_campaign_ids' => [
'type' => 'array',
'description' => 'Associates content with backlink campaigns',
'single' => true,
'show_in_rest'=> [
'schema' => [
'type' => 'array',
'items' => [
'type' => 'integer'
]
]
]
],
'_igny8_backlink_count' => [
'type' => 'integer',
'description' => 'Quick summary count of backlinks to content',
'single' => true,
'show_in_rest'=> true,
],
'_igny8_last_optimized' => [
'type' => 'string',
'description' => 'Tracks last optimization timestamp',
'single' => true,
'show_in_rest'=> true,
],
'_igny8_meta_title' => [
'type' => 'string',
'description' => 'SEO meta title for the content',
'single' => true,
'show_in_rest'=> true,
],
'_igny8_meta_description' => [
'type' => 'string',
'description' => 'SEO meta description for the content',
'single' => true,
'show_in_rest'=> true,
],
'_igny8_primary_keywords' => [
'type' => 'string',
'description' => 'Primary keywords for the content',
'single' => true,
'show_in_rest'=> true,
],
'_igny8_secondary_keywords' => [
'type' => 'string',
'description' => 'Secondary keywords for the content',
'single' => true,
'show_in_rest'=> true,
],
];
// Register each meta field for all relevant post types
foreach ($meta_fields as $meta_key => $config) {
foreach ($post_types as $post_type) {
register_post_meta($post_type, $meta_key, $config);
}
}
}
/**
* Set default plugin options
*/
function igny8_set_default_options() {
// Set default options if they don't exist
if (!get_option('igny8_api_key')) {
add_option('igny8_api_key', '');
}
if (!get_option('igny8_ai_enabled')) {
add_option('igny8_ai_enabled', 1);
}
if (!get_option('igny8_debug_enabled')) {
add_option('igny8_debug_enabled', 0);
}
if (!get_option('igny8_monitoring_enabled')) {
add_option('igny8_monitoring_enabled', 1);
}
if (!get_option('igny8_version')) {
add_option('igny8_version', '0.1');
}
}
/**
* Migrate logs table to add missing columns for OpenAI API logging
*/
function igny8_migrate_logs_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'igny8_logs';
// Check if table exists
if (!$wpdb->get_var("SHOW TABLES LIKE '$table_name'")) {
return true;
}
// Check if migration is needed
$columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name");
$column_names = array_column($columns, 'Field');
$needed_columns = ['api_id', 'status', 'level', 'source', 'user_id'];
$missing_columns = array_diff($needed_columns, $column_names);
if (empty($missing_columns)) {
return true; // Migration not needed
}
try {
// Add missing columns
if (in_array('api_id', $missing_columns)) {
$wpdb->query("ALTER TABLE `$table_name` ADD COLUMN `api_id` VARCHAR(255) NULL");
}
if (in_array('status', $missing_columns)) {
$wpdb->query("ALTER TABLE `$table_name` ADD COLUMN `status` VARCHAR(50) NULL");
}
if (in_array('level', $missing_columns)) {
$wpdb->query("ALTER TABLE `$table_name` ADD COLUMN `level` VARCHAR(50) NULL");
}
if (in_array('source', $missing_columns)) {
$wpdb->query("ALTER TABLE `$table_name` ADD COLUMN `source` VARCHAR(100) NULL");
}
if (in_array('user_id', $missing_columns)) {
$wpdb->query("ALTER TABLE `$table_name` ADD COLUMN `user_id` BIGINT UNSIGNED NULL");
}
// Add indexes for new columns
$indexes_to_add = [
'source' => "ALTER TABLE `$table_name` ADD INDEX `idx_source` (`source`)",
'status' => "ALTER TABLE `$table_name` ADD INDEX `idx_status` (`status`)",
'user_id' => "ALTER TABLE `$table_name` ADD INDEX `idx_user_id` (`user_id`)"
];
foreach ($indexes_to_add as $column => $sql) {
if (in_array($column, $missing_columns)) {
$wpdb->query($sql);
}
}
error_log('Igny8: Logs table migration completed successfully');
return true;
} catch (Exception $e) {
error_log('Igny8 Logs Migration Error: ' . $e->getMessage());
return false;
}
}
/**
* Complete plugin installation function
*/
function igny8_install_database() {
// Create all database tables
igny8_create_all_tables();
// Migrate logs table if needed
igny8_migrate_logs_table();
// Register taxonomies
igny8_register_taxonomies();
// Register post meta fields
igny8_register_post_meta();
// Set default options
igny8_set_default_options();
// Update version
update_option('igny8_version', '0.1');
update_option('igny8_db_version', '0.1');
// Add word_count field to tasks table if it doesn't exist
igny8_add_word_count_to_tasks();
// Add raw_ai_response field to tasks table if it doesn't exist
igny8_add_raw_ai_response_to_tasks();
// Add tasks_count field to content_ideas table if it doesn't exist
igny8_add_tasks_count_to_content_ideas();
// Add image_prompts field to content_ideas table if it doesn't exist
igny8_add_image_prompts_to_content_ideas();
// Update idea_description field to LONGTEXT for structured JSON descriptions
igny8_update_idea_description_to_longtext();
// Migrate ideas and tasks table structure
igny8_migrate_ideas_tasks_structure();
// Run legacy cleanup if needed
igny8_cleanup_legacy_structures();
}
/**
* Add word_count field to tasks table if it doesn't exist
*/
function igny8_add_word_count_to_tasks() {
global $wpdb;
// Check if word_count column exists
$column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_tasks LIKE 'word_count'");
if (empty($column_exists)) {
// Add word_count column
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks ADD COLUMN word_count INT UNSIGNED DEFAULT 0 AFTER keywords");
error_log('Igny8: Added word_count column to tasks table');
}
}
/**
* Add raw_ai_response field to tasks table if it doesn't exist
*/
function igny8_add_raw_ai_response_to_tasks() {
global $wpdb;
$column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_tasks LIKE 'raw_ai_response'");
if (empty($column_exists)) {
// Add raw_ai_response column
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks ADD COLUMN raw_ai_response LONGTEXT DEFAULT NULL AFTER word_count");
error_log('Igny8: Added raw_ai_response column to tasks table');
}
}
/**
* Add tasks_count field to content_ideas table if it doesn't exist
*/
function igny8_add_tasks_count_to_content_ideas() {
global $wpdb;
// Check if tasks_count column exists
$column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_content_ideas LIKE 'tasks_count'");
if (empty($column_exists)) {
// Add tasks_count column
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas ADD COLUMN tasks_count INT UNSIGNED DEFAULT 0 AFTER mapped_post_id");
error_log('Igny8: Added tasks_count column to content_ideas table');
}
}
/**
* Add image_prompts field to content_ideas table if it doesn't exist
*/
function igny8_add_image_prompts_to_content_ideas() {
global $wpdb;
// Check if image_prompts column exists
$column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_content_ideas LIKE 'image_prompts'");
if (empty($column_exists)) {
// Add image_prompts column
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas ADD COLUMN image_prompts TEXT DEFAULT NULL AFTER target_keywords");
error_log('Igny8: Added image_prompts column to content_ideas table');
}
}
/**
* Update idea_description field to LONGTEXT to support structured JSON descriptions
*/
function igny8_update_idea_description_to_longtext() {
global $wpdb;
// Check current column type
$column_info = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_content_ideas LIKE 'idea_description'");
if (!empty($column_info)) {
$column_type = $column_info[0]->Type;
// Only update if it's not already LONGTEXT
if (strpos(strtoupper($column_type), 'LONGTEXT') === false) {
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas MODIFY COLUMN idea_description LONGTEXT DEFAULT NULL");
error_log('Igny8: Updated idea_description column to LONGTEXT');
}
}
}
/**
* Migrate ideas and tasks table structure
*/
function igny8_migrate_ideas_tasks_structure() {
global $wpdb;
// Migrate ideas table
igny8_migrate_ideas_table();
// Migrate tasks table
igny8_migrate_tasks_table();
}
/**
* Migrate ideas table structure
*/
function igny8_migrate_ideas_table() {
global $wpdb;
// Check if idea_type column exists (old column) and remove it
$old_column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_content_ideas LIKE 'idea_type'");
if (!empty($old_column_exists)) {
// Drop the old idea_type column
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas DROP COLUMN idea_type");
error_log('Igny8: Removed idea_type column from ideas table');
}
// Check if content_structure column exists
$column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_content_ideas LIKE 'content_structure'");
if (empty($column_exists)) {
// Add content_structure column with new options
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas ADD COLUMN content_structure ENUM('cluster_hub','landing_page','guide_tutorial','how_to','comparison','review','top_listicle','question','product_description','service_page','home_page') DEFAULT 'cluster_hub' AFTER idea_description");
error_log('Igny8: Added content_structure column to ideas table');
} else {
// Update existing content_structure column with new options
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas MODIFY COLUMN content_structure ENUM('cluster_hub','landing_page','guide_tutorial','how_to','comparison','review','top_listicle','question','product_description','service_page','home_page') DEFAULT 'cluster_hub'");
error_log('Igny8: Updated content_structure column options in ideas table');
}
// Check if content_type column exists
$content_type_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_content_ideas LIKE 'content_type'");
if (empty($content_type_exists)) {
// Add content_type column
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas ADD COLUMN content_type ENUM('post','product','page','CPT') DEFAULT 'post' AFTER content_structure");
error_log('Igny8: Added content_type column to ideas table');
}
// Update indexes
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas DROP INDEX IF EXISTS idx_idea_type");
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas ADD INDEX idx_content_structure (content_structure)");
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_content_ideas ADD INDEX idx_content_type (content_type)");
}
/**
* Migrate tasks table structure
*/
function igny8_migrate_tasks_table() {
global $wpdb;
// Check if content_structure column exists
$column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_tasks LIKE 'content_structure'");
if (empty($column_exists)) {
// Check if content_type column exists (old column)
$old_column_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_tasks LIKE 'content_type'");
if (!empty($old_column_exists)) {
// Rename content_type to content_structure with new options
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks CHANGE COLUMN content_type content_structure ENUM('cluster_hub','landing_page','guide_tutorial','how_to','comparison','review','top_listicle','question','product_description','service_page','home_page') DEFAULT 'cluster_hub'");
error_log('Igny8: Renamed content_type to content_structure in tasks table');
} else {
// Add content_structure column with new options
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks ADD COLUMN content_structure ENUM('cluster_hub','landing_page','guide_tutorial','how_to','comparison','review','top_listicle','question','product_description','service_page','home_page') DEFAULT 'cluster_hub' AFTER due_date");
error_log('Igny8: Added content_structure column to tasks table');
}
} else {
// Update existing content_structure column with new options
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks MODIFY COLUMN content_structure ENUM('cluster_hub','landing_page','guide_tutorial','how_to','comparison','review','top_listicle','question','product_description','service_page','home_page') DEFAULT 'cluster_hub'");
error_log('Igny8: Updated content_structure column options in tasks table');
}
// Check if content_type column exists (new column)
$content_type_exists = $wpdb->get_results("SHOW COLUMNS FROM {$wpdb->prefix}igny8_tasks LIKE 'content_type'");
if (empty($content_type_exists)) {
// Add content_type column
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks ADD COLUMN content_type ENUM('post','product','page','CPT') DEFAULT 'post' AFTER content_structure");
error_log('Igny8: Added content_type column to tasks table');
}
// Update indexes
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks DROP INDEX IF EXISTS idx_content_type");
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks ADD INDEX idx_content_structure (content_structure)");
$wpdb->query("ALTER TABLE {$wpdb->prefix}igny8_tasks ADD INDEX idx_content_type (content_type)");
}

View File

@@ -0,0 +1,463 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : global-layout.php
* @location : /core/global-layout.php
* @type : Layout
* @scope : Global
* @allowed : HTML layout, CSS/JS includes, navigation rendering
* @reusability : Globally Reusable
* @notes : Master layout template for all admin pages
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Load helper functions
require_once plugin_dir_path(__FILE__) . 'admin/global-helpers.php';
// Evidence functions removed - no longer needed
// Component functions are loaded globally in igny8.php
// KPI configuration is loaded globally in igny8.php
// Load KPI data for header metrics based on current module/submodule
$kpi_data = [];
if (isset($GLOBALS['igny8_kpi_config']) && !empty($GLOBALS['igny8_kpi_config'])) {
$kpi_config = $GLOBALS['igny8_kpi_config'];
// Determine the table_id based on current module and submodule
$current_module = $GLOBALS['current_module'] ?? '';
$current_submodule = $GLOBALS['current_submodule'] ?? '';
$current_page = $_GET['page'] ?? '';
// Special handling for home pages
if ($current_page === 'igny8-planner' && empty($current_submodule)) {
$table_id = 'planner_home';
} elseif ($current_page === 'igny8-writer' && empty($current_submodule)) {
$table_id = 'writer_home';
} elseif (!empty($current_module) && !empty($current_submodule)) {
$table_id = $current_module . '_' . $current_submodule;
} else {
$table_id = '';
}
// Load KPI data if configuration exists for this table
if (!empty($table_id) && isset($kpi_config[$table_id])) {
$kpi_data = igny8_get_kpi_data_safe($table_id, $kpi_config[$table_id]);
}
}
?>
<div class="igny8-page-wrapper">
<!-- SIDEBAR SECTION -->
<aside class="igny8-sidebar">
<!-- LOGO / BRAND -->
<div class="igny8-sidebar-logo">
<h2>IGNY8 AI SEO</h2>
</div>
<!-- VERSION BADGE -->
<div class="igny8-version-badge">
<span class="igny8-badge igny8-btn-danger">v<?php echo get_plugin_data(plugin_dir_path(__FILE__) . '../igny8.php')['Version']; ?></span>
</div>
<!-- BREADCRUMB NAVIGATION -->
<div class="igny8-breadcrumb">
<?php echo igny8_render_breadcrumb(); ?>
</div>
<!-- DEBUG STATUS CIRCLES (submodule pages only) -->
<?php
$is_debug_enabled = defined('WP_DEBUG') && WP_DEBUG;
$is_monitoring_enabled = get_option('igny8_debug_enabled', false);
$is_submodule_page = !empty($_GET['sm']) || !empty($GLOBALS['current_submodule']);
if ($is_debug_enabled && $is_monitoring_enabled && $is_submodule_page):
// Simple debug circles - will be updated by JavaScript based on actual debug card states
$debug_circles = [
'database' => ['title' => 'Database', 'status' => 'secondary'],
'table' => ['title' => 'Table', 'status' => 'secondary'],
'filters' => ['title' => 'Filters', 'status' => 'secondary'],
'forms' => ['title' => 'Forms', 'status' => 'secondary'],
'automation' => ['title' => 'Automation', 'status' => 'secondary'],
'ai_logs' => ['title' => 'AI Logs', 'status' => 'secondary']
];
?>
<div class="igny8-sidebar-status-bar" style="padding: 12px 16px; border-bottom: 1px solid var(--border);">
<div class="igny8-status-row">
<div class="igny8-flex" style="justify-content: center; gap: 8px;">
<?php foreach ($debug_circles as $circle_key => $circle_data): ?>
<div class="bg-circle-sm bg-<?php echo esc_attr($circle_data['status']); ?> igny8-debug-circle" data-component="<?php echo esc_attr($circle_key); ?>" title="<?php echo esc_attr($circle_data['title']); ?>"></div>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endif; ?>
<!-- MAIN NAVIGATION -->
<nav class="igny8-sidebar-nav">
<?php
$current_page = $_GET['page'] ?? '';
$home_active = ($current_page === 'igny8-home') ? 'active' : '';
$settings_active = (strpos($current_page, 'igny8-settings') !== false) ? 'active' : '';
$help_active = (strpos($current_page, 'igny8-help') !== false) ? 'active' : '';
// Always show home, settings, and help
?>
<a href="<?php echo admin_url('admin.php?page=igny8-home'); ?>" class="igny8-sidebar-link <?php echo $home_active; ?>">
<span class="dashicons dashicons-dashboard"></span>
<span class="label">Igny8 Home</span>
</a>
<?php
// Show modules only if they are enabled
if (function_exists('igny8_is_module_enabled')) {
// Main modules
if (igny8_is_module_enabled('planner')) {
$planner_active = (strpos($current_page, 'igny8-planner') !== false) ? 'active' : '';
?>
<a href="<?php echo admin_url('admin.php?page=igny8-planner'); ?>" class="igny8-sidebar-link <?php echo $planner_active; ?>">
<span class="dashicons dashicons-tag"></span>
<span class="label">Planner</span>
</a>
<?php
}
if (igny8_is_module_enabled('writer')) {
$writer_active = (strpos($current_page, 'igny8-writer') !== false) ? 'active' : '';
?>
<a href="<?php echo admin_url('admin.php?page=igny8-writer'); ?>" class="igny8-sidebar-link <?php echo $writer_active; ?>">
<span class="dashicons dashicons-edit"></span>
<span class="label">Writer</span>
</a>
<?php
}
if (igny8_is_module_enabled('linker')) {
$linker_active = (strpos($current_page, 'igny8-linker') !== false) ? 'active' : '';
?>
<a href="<?php echo admin_url('admin.php?page=igny8-linker'); ?>" class="igny8-sidebar-link <?php echo $linker_active; ?>">
<span class="dashicons dashicons-admin-links"></span>
<span class="label">Linker</span>
</a>
<?php
}
if (igny8_is_module_enabled('personalize')) {
$personalize_active = (strpos($current_page, 'igny8-personalize') !== false) ? 'active' : '';
?>
<a href="<?php echo admin_url('admin.php?page=igny8-personalize'); ?>" class="igny8-sidebar-link <?php echo $personalize_active; ?>">
<span class="dashicons dashicons-admin-customizer"></span>
<span class="label">Personalize</span>
</a>
<div class="igny8-sidebar-divider"></div> <?php
}
?>
<?php
// Thinker before schedules
if (igny8_is_module_enabled('thinker')) {
$thinker_active = (strpos($current_page, 'igny8-thinker') !== false) ? 'active' : '';
?>
<a href="<?php echo admin_url('admin.php?page=igny8-thinker'); ?>" class="igny8-sidebar-link <?php echo $thinker_active; ?>">
<span class="dashicons dashicons-lightbulb"></span>
<span class="label">Thinker</span>
</a>
<?php
}
// Admin modules
if (igny8_is_module_enabled('schedules')) {
$schedules_active = (strpos($current_page, 'igny8-schedules') !== false) ? 'active' : '';
?>
<a href="<?php echo admin_url('admin.php?page=igny8-schedules'); ?>" class="igny8-sidebar-link <?php echo $schedules_active; ?>">
<span class="dashicons dashicons-calendar-alt"></span>
<span class="label">Schedules</span>
</a>
<?php
}
// Analytics before Settings
if (igny8_is_module_enabled('analytics')) {
$analytics_active = (strpos($current_page, 'igny8-analytics') !== false) ? 'active' : '';
?>
<a href="<?php echo admin_url('admin.php?page=igny8-analytics'); ?>" class="igny8-sidebar-link <?php echo $analytics_active; ?>">
<span class="dashicons dashicons-chart-line"></span>
<span class="label">Analytics</span>
</a>
<?php
}
} else {
// Fallback: show all modules if module manager is not available
?>
<a href="<?php echo admin_url('admin.php?page=igny8-planner'); ?>" class="igny8-sidebar-link <?php echo (strpos($current_page, 'igny8-planner') !== false) ? 'active' : ''; ?>">
<span class="dashicons dashicons-tag"></span>
<span class="label">Planner</span>
</a>
<a href="<?php echo admin_url('admin.php?page=igny8-writer'); ?>" class="igny8-sidebar-link <?php echo (strpos($current_page, 'igny8-writer') !== false) ? 'active' : ''; ?>">
<span class="dashicons dashicons-edit"></span>
<span class="label">Writer</span>
</a>
<a href="<?php echo admin_url('admin.php?page=igny8-optimizer'); ?>" class="igny8-sidebar-link <?php echo (strpos($current_page, 'igny8-optimizer') !== false) ? 'active' : ''; ?>">
<span class="dashicons dashicons-performance"></span>
<span class="label">Optimizer</span>
</a>
<a href="<?php echo admin_url('admin.php?page=igny8-linker'); ?>" class="igny8-sidebar-link <?php echo (strpos($current_page, 'igny8-linker') !== false) ? 'active' : ''; ?>">
<span class="dashicons dashicons-admin-links"></span>
<span class="label">Linker</span>
</a>
<a href="<?php echo admin_url('admin.php?page=igny8-personalize'); ?>" class="igny8-sidebar-link <?php echo (strpos($current_page, 'igny8-personalize') !== false) ? 'active' : ''; ?>">
<span class="dashicons dashicons-admin-customizer"></span>
<span class="label">Personalize</span>
</a>
<div class="igny8-sidebar-divider"></div>
<a href="<?php echo admin_url('admin.php?page=igny8-thinker'); ?>" class="igny8-sidebar-link <?php echo (strpos($current_page, 'igny8-thinker') !== false) ? 'active' : ''; ?>">
<span class="dashicons dashicons-lightbulb"></span>
<span class="label">Thinker</span>
</a>
<a href="<?php echo admin_url('admin.php?page=igny8-schedules'); ?>" class="igny8-sidebar-link <?php echo (strpos($current_page, 'igny8-schedules') !== false) ? 'active' : ''; ?>">
<span class="dashicons dashicons-calendar-alt"></span>
<span class="label">Schedules</span>
</a>
<a href="<?php echo admin_url('admin.php?page=igny8-analytics'); ?>" class="igny8-sidebar-link <?php echo (strpos($current_page, 'igny8-analytics') !== false) ? 'active' : ''; ?>">
<span class="dashicons dashicons-chart-line"></span>
<span class="label">Analytics</span>
</a>
<?php
}
?>
<a href="<?php echo admin_url('admin.php?page=igny8-settings'); ?>" class="igny8-sidebar-link <?php echo $settings_active; ?>">
<span class="dashicons dashicons-admin-generic"></span>
<span class="label">Settings</span>
</a>
<a href="<?php echo admin_url('admin.php?page=igny8-help'); ?>" class="igny8-sidebar-link <?php echo $help_active; ?>">
<span class="dashicons dashicons-sos"></span>
<span class="label">Help</span>
</a>
</nav>
<div class="igny8-sidebar-divider"></div>
<!-- JavaScript to update sidebar debug circles based on debug card states -->
<?php if ($is_debug_enabled && $is_monitoring_enabled && $is_submodule_page): ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Function to update sidebar debug circles based on debug card states
function updateSidebarDebugCircles() {
const circles = document.querySelectorAll('.igny8-debug-circle');
circles.forEach(circle => {
const component = circle.getAttribute('data-component');
let matchingCard = null;
if (component === 'ai_logs') {
// Special handling for AI logs - it's in the automation section
const debugCards = document.querySelectorAll('.igny8-debug-item');
debugCards.forEach(card => {
const cardText = card.textContent.toLowerCase();
if (cardText.includes('ai logs') || cardText.includes('ai_logs')) {
matchingCard = card;
}
});
} else {
// Regular handling for other components
const debugCards = document.querySelectorAll('.igny8-debug-item');
debugCards.forEach(card => {
const cardText = card.textContent.toLowerCase();
const componentName = component.toLowerCase();
if (cardText.includes(componentName)) {
matchingCard = card;
}
});
}
if (matchingCard) {
// Get the status from the background color
const bgColor = matchingCard.style.backgroundColor;
let status = 'secondary'; // default
if (bgColor.includes('212, 237, 218') || bgColor.includes('rgb(212, 237, 218)')) { // success green
status = 'success';
} else if (bgColor.includes('255, 243, 205') || bgColor.includes('rgb(255, 243, 205)')) { // warning yellow
status = 'warning';
} else if (bgColor.includes('248, 215, 218') || bgColor.includes('rgb(248, 215, 218)')) { // error red
status = 'danger';
}
// Update circle classes
circle.className = `bg-circle-sm bg-${status} igny8-debug-circle`;
}
});
}
// Update circles when page loads
setTimeout(updateSidebarDebugCircles, 1000); // Wait for debug cards to load
// Update circles periodically to catch dynamic changes
setInterval(updateSidebarDebugCircles, 3000);
});
</script>
<?php endif; ?>
<!-- FOOTER SHORTCUTS -->
<div class="igny8-sidebar-footer-container">
<div class="igny8-sidebar-footer">
<a href="<?php echo admin_url('options-general.php'); ?>" class="igny8-sidebar-link">
<span class="dashicons dashicons-admin-generic"></span>
<span class="label">Settings</span>
</a>
<a href="<?php echo wp_logout_url(); ?>" class="igny8-sidebar-link">
<span class="dashicons dashicons-migrate"></span>
<span class="label">Logout</span>
</a>
</div>
</div>
</aside>
<!-- MAIN CONTAINER -->
<div class="igny8-main-area">
<!-- HEADER SECTION -->
<header class="igny8-header">
<!-- LEFT: Dynamic Submenu Navigation -->
<div class="igny8-header-left">
<div class="igny8-submenu">
<div class="igny8-submenu-buttons">
<?php echo igny8_render_submenu(); ?>
</div>
</div>
</div>
<!-- CENTER: Page Title & Description -->
<div class="igny8-header-center">
<div class="igny8-page-title">
<h1><?php
// Hardcoded page titles
$page_titles = [
'igny8-home' => 'Dashboard',
'igny8-planner' => 'Planner',
'igny8-writer' => 'Writer',
'igny8-thinker' => 'Thinker',
'igny8-analytics' => 'Analytics',
'igny8-settings' => 'Settings',
'igny8-schedules' => 'Schedules',
'igny8-help' => 'Help'
];
echo $page_titles[$current_page] ?? 'IGNY8 AI SEO';
?></h1>
</div>
</div>
<!-- RIGHT: Metrics Bar + Icons -->
<div class="igny8-header-right">
<!-- Metrics - Compact Format -->
<div class="igny8-metrics-compact">
<?php
// Use the same KPI logic as before but inline
if (empty($kpi_data)) {
$max_fallback = ($current_module === 'planner') ? 4 : 6;
$all_fallback_metrics = [
['value' => 0, 'label' => 'Active', 'color' => 'green'],
['value' => 0, 'label' => 'Pending', 'color' => 'amber'],
['value' => 0, 'label' => 'Completed', 'color' => 'purple'],
['value' => 0, 'label' => 'Recent', 'color' => 'blue']
];
$fallback_metrics = array_slice($all_fallback_metrics, 0, $max_fallback);
foreach ($fallback_metrics as $metric):
?>
<div class="metric <?php echo $metric['color']; ?>">
<span class="val"><?php echo number_format($metric['value']); ?></span>
<span class="lbl"><?php echo $metric['label']; ?></span>
</div>
<?php
endforeach;
} else {
// Get metrics - 4 for planner pages, 6 for others
$max_metrics = ($current_module === 'planner') ? 4 : 6;
$metrics = array_slice($kpi_data, 0, $max_metrics, true);
$color_map = ['', 'green', 'amber', 'purple', 'blue', 'teal'];
$color_index = 0;
foreach ($metrics as $metric_key => $metric_value):
$kpi_config = $GLOBALS['igny8_kpi_config'] ?? [];
$color = '';
if (isset($kpi_config[$table_id][$metric_key]['color'])) {
$color = $kpi_config[$table_id][$metric_key]['color'];
} else {
$color = $color_map[$color_index] ?? '';
}
$label = $metric_key;
if (isset($kpi_config[$table_id][$metric_key]['label'])) {
$label = $kpi_config[$table_id][$metric_key]['label'];
}
?>
<div class="metric <?php echo $color; ?>">
<span class="val"><?php echo number_format($metric_value); ?></span>
<span class="lbl"><?php echo $label; ?></span>
</div>
<?php
$color_index++;
endforeach;
}
?>
</div>
<!-- Header User Icon -->
<div class="igny8-header-icons">
<span class="dashicons dashicons-admin-users" title="User"></span>
</div>
</div>
</header>
<!-- MAIN CONTENT AREA -->
<main class="igny8-content">
<?php echo $igny8_page_content ?? '<p>No content provided.</p>'; ?>
<!-- MODULE DEBUG SECTION (conditional based on toggle and submodule pages) -->
<?php
// Only show module debug if:
// 1. WP_DEBUG is enabled
// 2. Debug monitoring toggle is enabled
// 3. We're on a submodule page (not home page)
$is_debug_enabled = defined('WP_DEBUG') && WP_DEBUG;
$is_monitoring_enabled = get_option('igny8_debug_enabled', false);
$is_submodule_page = !empty($_GET['sm']) || !empty($GLOBALS['current_submodule']);
if ($is_debug_enabled && $is_monitoring_enabled && $is_submodule_page) {
require_once plugin_dir_path(__FILE__) . '../debug/module-debug.php';
$debug_content = igny8_get_module_debug_content();
if (!empty($debug_content)) {
echo $debug_content;
}
}
?>
</main>
<!-- FOOTER SECTION -->
<footer class="igny8-footer">
<div class="igny8-footer-content">
<span>&copy; <?php echo date('Y'); ?> Igny8 Plugin</span>
</div>
</footer>
</div>
</div>

View File

@@ -0,0 +1,14 @@
<?php
/**
* ==============================
* 📁 Folder Scope Declaration
* ==============================
* Folder: /debug/
* Purpose: Dev-only tools: logs, CRON tests, function runners
* Rules:
* - Must be wrapped in IGNY8_DEV guard
* - Development and testing tools only
* - Must not be loaded in production
* - Debug and monitoring utilities
* - Temporary testing functions
*/

View File

@@ -0,0 +1,47 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : debug.php
* @location : /debug/debug.php
* @type : Debug Tool
* @scope : Global
* @allowed : Debug functionality, development tools
* @reusability : Single Use
* @notes : Debug page (functionality moved to status page)
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Dev-only access guard
if (!defined('IGNY8_DEV') || IGNY8_DEV !== true) {
return;
}
// Set page variables for global layout
$current_submodule = 'debug';
$show_subnav = false;
$subnav_items = [];
// Set the page content function for global layout
function igny8_get_debug_page_content() {
ob_start();
?>
<div class="igny8-card">
<div class="igny8-card-body" style="text-align: center; padding: 40px;">
<h2 style="color: var(--text-dim); margin-bottom: 16px;">Debug Functionality Moved</h2>
<p style="color: var(--text-dim); font-size: 16px; margin-bottom: 20px;">
All debug monitoring functionality has been moved to the <strong>Settings > Status</strong> page.
</p>
<a href="<?php echo admin_url('admin.php?page=igny8-settings&sp=status'); ?>" class="igny8-btn igny8-btn-primary">
Go to Status Page
</a>
</div>
</div>
<?php
return ob_get_clean();
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : monitor-helpers.php
* @location : /debug/monitor-helpers.php
* @type : Debug Tool
* @scope : Global
* @allowed : Monitoring functions, diagnostic tools
* @reusability : Single Use
* @notes : Backend functions for diagnostic monitoring system
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Dev-only access guard
if (!defined('IGNY8_DEV') || IGNY8_DEV !== true) {
return;
}

View File

@@ -0,0 +1,348 @@
# Igny8 AI SEO Plugin - Complete Features List
## Summary Table
| Module | Core Features | Functions Involved | Dependencies | Files Involved |
|--------|---------------|-------------------|--------------|----------------|
| **Planner** | Keyword Research, AI Clustering, Content Ideas | `igny8_research_keywords()`, `igny8_ai_cluster_keywords()`, `igny8_ai_generate_ideas()` | OpenAI API, Database, WordPress | `modules/modules-pages/planner.php`, `ai/modules-ai.php`, `flows/sync-functions.php` |
| **Writer** | Content Generation, Draft Management, Publishing | `igny8_generate_content()`, `igny8_create_draft()`, `igny8_publish_content()` | AI APIs, WordPress, Database | `modules/modules-pages/writer.php`, `ai/modules-ai.php`, `flows/sync-functions.php` |
| **Optimizer** | SEO Analysis, Content Audits, Optimization | `igny8_analyze_content()`, `igny8_generate_suggestions()`, `igny8_optimize_content()` | SEO APIs, Database | `modules/modules-pages/optimizer.php`, `ai/modules-ai.php` |
| **Linker** | Backlink Management, Campaign Tracking | `igny8_track_backlinks()`, `igny8_manage_campaigns()`, `igny8_analyze_authority()` | External APIs, Database | `modules/modules-pages/linker.php`, `flows/sync-functions.php` |
| **Personalize** | AI Content Personalization, User Targeting | `igny8_detect_fields()`, `igny8_rewrite_content()`, `igny8_personalize_content()` | OpenAI API, Frontend | `modules/modules-pages/personalize/`, `ai/integration.php` |
| **Thinker** | AI Strategy, Prompt Management, Content Planning | `igny8_manage_prompts()`, `igny8_generate_strategies()`, `igny8_plan_content()` | AI APIs, Database | `core/pages/thinker/`, `ai/prompts-library.php` |
| **Analytics** | Performance Tracking, KPI Monitoring | `igny8_track_metrics()`, `igny8_generate_reports()`, `igny8_analyze_performance()` | Database, WordPress | `core/admin/`, `modules/config/kpi-config.php` |
| **Schedules** | Automation Management, CRON Jobs | `igny8_manage_cron()`, `igny8_schedule_tasks()`, `igny8_automate_workflows()` | WordPress CRON, Database | `core/cron/`, `core/pages/settings/schedules.php` |
---
## 1. PLANNER MODULE FEATURES
### 1.1 Keyword Research & Analysis
- **Manual Keyword Import**: CSV import with template support
- **Keyword Validation**: Duplicate detection and data validation
- **Keyword Metrics**: Volume, difficulty, competition analysis
- **Keyword Categorization**: Primary, secondary, long-tail classification
- **Keyword Mapping**: Cluster assignment and relationship mapping
### 1.2 AI-Powered Clustering
- **Automatic Clustering**: AI-driven keyword grouping based on semantic similarity
- **Cluster Management**: Create, edit, delete, and merge clusters
- **Cluster Metrics**: Volume aggregation, keyword count, performance tracking
- **Cluster Optimization**: AI suggestions for cluster improvement
- **Sector-Based Clustering**: Industry-specific keyword organization
### 1.3 Content Idea Generation
- **AI Idea Creation**: Automated content idea generation based on clusters
- **Idea Categorization**: Content type classification and tagging
- **Idea Prioritization**: AI-driven priority scoring and ranking
- **Idea Expansion**: Related topic and angle suggestions
- **Idea Validation**: Quality assessment and feasibility analysis
### 1.4 Planning Workflow Management
- **Step-by-Step Guidance**: Interactive planning workflow
- **Progress Tracking**: Visual progress indicators and completion status
- **Task Management**: Planning task assignment and tracking
- **Workflow Automation**: Automated progression through planning steps
- **Planning Analytics**: Performance metrics and optimization insights
---
## 2. WRITER MODULE FEATURES
### 2.1 Content Generation
- **AI Content Creation**: Automated article generation using OpenAI
- **Content Templates**: Predefined content structures and formats
- **Content Variation**: Multiple content versions and angles
- **Content Optimization**: SEO-optimized content generation
- **Content Personalization**: User-specific content adaptation
### 2.2 Draft Management
- **Draft Creation**: Automated draft generation from content ideas
- **Draft Review**: Content quality assessment and editing interface
- **Draft Versioning**: Version control and change tracking
- **Draft Collaboration**: Multi-user editing and approval workflows
- **Draft Optimization**: AI-powered content improvement suggestions
### 2.3 Publishing Workflow
- **Automated Publishing**: Scheduled content publication
- **Publishing Validation**: Pre-publication quality checks
- **Publishing Analytics**: Post-publication performance tracking
- **Bulk Publishing**: Mass content publication with batch processing
- **Publishing Optimization**: Performance-based publishing adjustments
### 2.4 Content Quality Assurance
- **Readability Analysis**: Content readability scoring and improvement
- **SEO Optimization**: Automated SEO factor optimization
- **Content Validation**: Grammar, spelling, and quality checks
- **Content Scoring**: Overall content quality assessment
- **Content Improvement**: AI-powered content enhancement suggestions
---
## 3. OPTIMIZER MODULE FEATURES
### 3.1 SEO Analysis
- **Content Audits**: Comprehensive SEO analysis of existing content
- **Keyword Optimization**: Keyword density and placement analysis
- **Meta Tag Analysis**: Title, description, and header optimization
- **Technical SEO**: Site structure and performance analysis
- **Competitor Analysis**: Competitive SEO benchmarking
### 3.2 Optimization Suggestions
- **AI-Powered Recommendations**: Intelligent optimization suggestions
- **Content Improvement**: Specific content enhancement recommendations
- **SEO Factor Optimization**: Technical SEO improvement suggestions
- **Performance Optimization**: Speed and user experience improvements
- **Conversion Optimization**: User engagement and conversion improvements
### 3.3 Performance Monitoring
- **Ranking Tracking**: Keyword ranking monitoring and analysis
- **Traffic Analysis**: Organic traffic growth and source analysis
- **Engagement Metrics**: User engagement and interaction tracking
- **Conversion Tracking**: Goal completion and conversion rate monitoring
- **Performance Reporting**: Comprehensive performance analytics and reporting
---
## 4. LINKER MODULE FEATURES
### 4.1 Backlink Management
- **Backlink Discovery**: Automated backlink detection and tracking
- **Backlink Analysis**: Quality assessment and authority analysis
- **Backlink Monitoring**: Continuous backlink tracking and updates
- **Backlink Reporting**: Comprehensive backlink performance reports
- **Backlink Optimization**: Link building strategy recommendations
### 4.2 Campaign Management
- **Campaign Planning**: Strategic link building campaign development
- **Outreach Management**: Automated outreach and follow-up systems
- **Campaign Tracking**: Progress monitoring and performance analysis
- **Campaign Optimization**: Performance-based campaign adjustments
- **Campaign Reporting**: Detailed campaign analytics and insights
### 4.3 Authority Building
- **Domain Authority Tracking**: DA monitoring and improvement strategies
- **Link Quality Assessment**: High-quality link identification and targeting
- **Relationship Building**: Influencer and industry relationship management
- **Content Promotion**: Strategic content promotion and link acquisition
- **Authority Optimization**: Long-term authority building strategies
---
## 5. PERSONALIZE MODULE FEATURES
### 5.1 AI Content Personalization
- **Field Detection**: Automated user characteristic identification
- **Content Rewriting**: AI-powered content personalization
- **User Targeting**: Demographic and behavioral targeting
- **Content Variation**: Multiple personalized content versions
- **Personalization Analytics**: User engagement and conversion tracking
### 5.2 Frontend Integration
- **Shortcode Implementation**: Easy content personalization integration
- **Display Modes**: Button, inline, and automatic personalization modes
- **User Interface**: Customizable personalization forms and interfaces
- **Responsive Design**: Mobile-optimized personalization experience
- **Performance Optimization**: Fast-loading personalization features
### 5.3 Personalization Management
- **Prompt Configuration**: AI prompt customization and optimization
- **Field Management**: Custom field creation and management
- **Content Scope**: Configurable content personalization scope
- **Context Management**: Advanced context and targeting options
- **Performance Monitoring**: Personalization effectiveness tracking
---
## 6. THINKER MODULE FEATURES
### 6.1 AI Strategy Management
- **Prompt Library**: Comprehensive AI prompt collection and management
- **Strategy Development**: AI-powered content strategy creation
- **Content Planning**: Intelligent content planning and scheduling
- **Performance Analysis**: Strategy effectiveness analysis and optimization
- **Strategic Recommendations**: AI-driven strategic insights and suggestions
### 6.2 Prompt Management
- **Prompt Creation**: Custom AI prompt development and testing
- **Prompt Optimization**: Performance-based prompt improvement
- **Prompt Library**: Organized prompt collection and categorization
- **Prompt Testing**: A/B testing for prompt effectiveness
- **Prompt Analytics**: Prompt performance tracking and analysis
### 6.3 Content Strategy
- **Strategic Planning**: Long-term content strategy development
- **Content Calendar**: Automated content scheduling and planning
- **Strategy Optimization**: Performance-based strategy adjustments
- **Competitive Analysis**: Competitor strategy analysis and benchmarking
- **Strategic Reporting**: Comprehensive strategy performance reports
---
## 7. ANALYTICS MODULE FEATURES
### 7.1 Performance Tracking
- **KPI Monitoring**: Key performance indicator tracking and analysis
- **Metric Dashboards**: Real-time performance dashboards
- **Performance Alerts**: Automated performance threshold alerts
- **Trend Analysis**: Performance trend identification and analysis
- **Comparative Analysis**: Period-over-period performance comparison
### 7.2 Reporting System
- **Automated Reports**: Scheduled performance reports
- **Custom Reports**: User-defined report creation and customization
- **Export Functionality**: Multiple export formats (PDF, CSV, Excel)
- **Report Scheduling**: Automated report delivery and distribution
- **Report Analytics**: Report usage and effectiveness tracking
### 7.3 Data Visualization
- **Interactive Charts**: Dynamic performance charts and graphs
- **Dashboard Customization**: Personalized dashboard configuration
- **Real-time Updates**: Live performance data updates
- **Visual Analytics**: Advanced data visualization and analysis
- **Performance Insights**: AI-powered performance insights and recommendations
---
## 8. SCHEDULES MODULE FEATURES
### 8.1 Automation Management
- **CRON Job Management**: Automated task scheduling and execution
- **Workflow Automation**: End-to-end process automation
- **Task Scheduling**: Flexible task scheduling and management
- **Automation Monitoring**: Automation performance tracking and optimization
- **Error Handling**: Robust error handling and recovery systems
### 8.2 Schedule Configuration
- **Custom Schedules**: User-defined automation schedules
- **Schedule Optimization**: Performance-based schedule adjustments
- **Schedule Validation**: Schedule conflict detection and resolution
- **Schedule Reporting**: Automation performance and effectiveness reports
- **Schedule Management**: Centralized schedule administration and control
### 8.3 Process Automation
- **Content Automation**: Automated content creation and publishing
- **SEO Automation**: Automated SEO optimization and monitoring
- **Link Building Automation**: Automated link building and outreach
- **Analytics Automation**: Automated reporting and analysis
- **Maintenance Automation**: Automated system maintenance and optimization
---
## 9. CORE SYSTEM FEATURES
### 9.1 Database Management
- **Custom Tables**: 15+ specialized database tables
- **Data Migration**: Seamless data migration and version management
- **Data Validation**: Comprehensive data integrity and validation
- **Performance Optimization**: Database performance tuning and optimization
- **Backup & Recovery**: Automated backup and disaster recovery systems
### 9.2 WordPress Integration
- **Post Meta Management**: Advanced post metadata handling
- **Taxonomy Integration**: Custom taxonomy creation and management
- **User Management**: User role and permission management
- **Plugin Compatibility**: WordPress plugin ecosystem integration
- **Theme Integration**: Seamless theme integration and customization
### 9.3 AI Integration
- **OpenAI Integration**: Advanced AI content generation
- **Runware Integration**: AI-powered image generation
- **Model Management**: AI model selection and optimization
- **Cost Tracking**: AI usage cost monitoring and optimization
- **Performance Monitoring**: AI service performance tracking
### 9.4 Security & Performance
- **Security Hardening**: Comprehensive security measures and protection
- **Performance Optimization**: System performance tuning and optimization
- **Error Handling**: Robust error handling and logging
- **Monitoring**: System health monitoring and alerting
- **Scalability**: High-performance, scalable architecture
---
## 10. ADVANCED FEATURES
### 10.1 Machine Learning
- **Predictive Analytics**: AI-powered performance prediction
- **Pattern Recognition**: Content and user behavior pattern analysis
- **Recommendation Engine**: Intelligent content and strategy recommendations
- **Anomaly Detection**: Unusual performance pattern identification
- **Continuous Learning**: Self-improving AI systems
### 10.2 Integration Capabilities
- **API Integration**: Third-party service integration
- **Webhook Support**: Real-time data synchronization
- **Export/Import**: Comprehensive data portability
- **Custom Integrations**: Flexible integration development
- **Data Synchronization**: Multi-platform data consistency
### 10.3 Enterprise Features
- **Multi-user Support**: Team collaboration and management
- **Role-based Access**: Granular permission and access control
- **Audit Logging**: Comprehensive activity tracking and logging
- **Compliance**: Industry standard compliance and security
- **Support**: Enterprise-level support and maintenance
---
## Technical Dependencies
### Core Dependencies
- **WordPress**: 5.0+ (Core platform)
- **PHP**: 7.4+ (Server-side language)
- **MySQL**: 5.7+ (Database system)
- **JavaScript**: ES6+ (Client-side functionality)
### AI Dependencies
- **OpenAI API**: GPT-4, GPT-3.5-turbo (Content generation)
- **Runware API**: Image generation and processing
- **cURL**: HTTP client for API communication
- **JSON**: Data format for AI communication
### WordPress Dependencies
- **WordPress Hooks**: Actions and filters
- **WordPress Database**: Custom tables and queries
- **WordPress Admin**: Admin interface integration
- **WordPress CRON**: Scheduled task management
### External Dependencies
- **cURL**: HTTP requests and API communication
- **JSON**: Data serialization and communication
- **CSV**: Data import/export functionality
- **REST API**: WordPress REST API integration
---
## File Structure Summary
### Core Files
- `igny8.php` - Main plugin file
- `install.php` - Installation script
- `uninstall.php` - Uninstallation script
- `igny8-wp-load-handler.php` - WordPress load handler
### Module Files
- `modules/modules-pages/` - Module interfaces
- `modules/components/` - Reusable components
- `modules/config/` - Configuration files
### AI Integration
- `ai/integration.php` - AI service integration
- `ai/openai-api.php` - OpenAI API client
- `ai/runware-api.php` - Runware API client
- `ai/modules-ai.php` - AI module functions
### Core System
- `core/admin/` - Admin interface
- `core/db/` - Database management
- `core/cron/` - Automation system
- `core/pages/` - Page templates
### Workflows
- `flows/` - Workflow automation
- `assets/` - Frontend assets
- `docs/` - Documentation
This comprehensive features list covers all aspects of the Igny8 AI SEO Plugin, providing a complete overview of its capabilities, dependencies, and technical architecture.

View File

@@ -0,0 +1,726 @@
# Igny8 AI SEO Plugin - Complete Function Reference
## Summary Table
| Function Category | Function Count | Core Functions | Dependencies | Files Involved |
|------------------|----------------|----------------|--------------|----------------|
| **Core System** | 45+ | `igny8_init()`, `igny8_register_settings()`, `igny8_create_all_tables()` | WordPress, Database | `igny8.php`, `core/admin/init.php`, `core/db/db.php` |
| **AI Integration** | 25+ | `igny8_openai_request()`, `igny8_runware_request()`, `igny8_generate_content()` | OpenAI API, Runware API | `ai/integration.php`, `ai/openai-api.php`, `ai/runware-api.php` |
| **Database Management** | 30+ | `igny8_create_all_tables()`, `igny8_migrate_data()`, `igny8_cleanup_legacy_structures()` | MySQL, WordPress | `core/db/db.php`, `core/db/db-migration.php` |
| **Workflow Automation** | 40+ | `igny8_sync_post_meta_data()`, `igny8_inject_responsive_images_separate()`, `igny8_ajax_bulk_publish_drafts()` | WordPress, Database | `flows/sync-functions.php`, `flows/sync-ajax.php`, `flows/image-injection-responsive.php` |
| **Module Management** | 20+ | `igny8_is_module_enabled()`, `igny8_get_enabled_modules()`, `igny8_register_module()` | WordPress, Database | `core/admin/module-manager-class.php`, `core/admin/init.php` |
| **CRON Automation** | 15+ | `igny8_master_dispatcher_run()`, `igny8_process_ai_queue_cron_handler()`, `igny8_auto_cluster_cron_handler()` | WordPress CRON, Database | `core/cron/igny8-cron-master-dispatcher.php`, `core/cron/igny8-cron-handlers.php` |
| **Admin Interface** | 35+ | `igny8_render_table()`, `igny8_render_filters()`, `igny8_render_pagination()` | WordPress Admin, JavaScript | `core/admin/`, `modules/components/` |
| **Analytics & KPI** | 25+ | `igny8_get_kpi_data_safe()`, `igny8_track_metrics()`, `igny8_generate_reports()` | Database, WordPress | `core/admin/global-helpers.php`, `modules/config/kpi-config.php` |
---
## 1. CORE SYSTEM FUNCTIONS
### 1.1 Plugin Initialization Functions
#### `igny8_init()`
- **Purpose**: Main plugin initialization function
- **Dependencies**: WordPress hooks, database setup
- **Files**: `igny8.php`
- **Functionality**: Plugin setup, hook registration, module initialization
- **Returns**: Boolean success status
#### `igny8_register_settings()`
- **Purpose**: WordPress settings registration
- **Dependencies**: WordPress settings API
- **Files**: `core/admin/init.php`
- **Functionality**: Settings group and field registration
- **Returns**: Boolean registration status
#### `igny8_init_wordpress_features()`
- **Purpose**: WordPress feature initialization
- **Dependencies**: WordPress core, taxonomies, post meta
- **Files**: `core/admin/init.php`
- **Functionality**: Taxonomy registration, post meta setup
- **Returns**: Boolean initialization status
### 1.2 Database Management Functions
#### `igny8_create_all_tables()`
- **Purpose**: Create all custom database tables
- **Dependencies**: WordPress database, table schemas
- **Files**: `core/db/db.php`
- **Functionality**: Table creation, schema setup
- **Returns**: Boolean creation status
#### `igny8_register_taxonomies()`
- **Purpose**: Register custom taxonomies
- **Dependencies**: WordPress taxonomy system
- **Files**: `core/db/db.php`
- **Functionality**: Taxonomy registration, term setup
- **Returns**: Boolean registration status
#### `igny8_register_post_meta()`
- **Purpose**: Register post meta fields
- **Dependencies**: WordPress post meta system
- **Files**: `core/db/db.php`
- **Functionality**: Meta field registration, validation setup
- **Returns**: Boolean registration status
#### `igny8_set_default_options()`
- **Purpose**: Set default plugin options
- **Dependencies**: WordPress options API
- **Files**: `core/db/db.php`
- **Functionality**: Default option values setup
- **Returns**: Boolean setup status
### 1.3 Migration Functions
#### `igny8_migrate_logs_table()`
- **Purpose**: Migrate logs table structure
- **Dependencies**: Database system, migration scripts
- **Files**: `core/db/db-migration.php`
- **Functionality**: Table structure migration
- **Returns**: Boolean migration status
#### `igny8_add_word_count_to_tasks()`
- **Purpose**: Add word count column to tasks table
- **Dependencies**: Database system, table structure
- **Files**: `core/db/db-migration.php`
- **Functionality**: Column addition, data migration
- **Returns**: Boolean addition status
#### `igny8_add_tasks_count_to_content_ideas()`
- **Purpose**: Add tasks count to content ideas
- **Dependencies**: Database system, content ideas table
- **Files**: `core/db/db-migration.php`
- **Functionality**: Column addition, count calculation
- **Returns**: Boolean addition status
#### `igny8_add_image_prompts_to_content_ideas()`
- **Purpose**: Add image prompts to content ideas
- **Dependencies**: Database system, content ideas table
- **Files**: `core/db/db-migration.php`
- **Functionality**: Column addition, prompt setup
- **Returns**: Boolean addition status
#### `igny8_update_idea_description_to_longtext()`
- **Purpose**: Update idea description to longtext
- **Dependencies**: Database system, content ideas table
- **Files**: `core/db/db-migration.php`
- **Functionality**: Column type update, data preservation
- **Returns**: Boolean update status
#### `igny8_cleanup_legacy_structures()`
- **Purpose**: Clean up legacy database structures
- **Dependencies**: Database system, legacy data
- **Files**: `core/db/db-migration.php`
- **Functionality**: Legacy cleanup, data migration
- **Returns**: Boolean cleanup status
---
## 2. AI INTEGRATION FUNCTIONS
### 2.1 OpenAI API Functions
#### `igny8_openai_request()`
- **Purpose**: Make OpenAI API requests
- **Dependencies**: OpenAI API, authentication
- **Files**: `ai/openai-api.php`
- **Functionality**: API communication, response handling
- **Returns**: API response data
#### `igny8_generate_content()`
- **Purpose**: Generate AI content using OpenAI
- **Dependencies**: OpenAI API, content data
- **Files**: `ai/modules-ai.php`
- **Functionality**: Content generation, prompt processing
- **Returns**: Generated content
#### `igny8_ai_cluster_keywords()`
- **Purpose**: AI-powered keyword clustering
- **Dependencies**: OpenAI API, keyword data
- **Files**: `ai/modules-ai.php`
- **Functionality**: Semantic clustering, keyword grouping
- **Returns**: Clustered keywords
#### `igny8_ai_generate_ideas()`
- **Purpose**: Generate content ideas using AI
- **Dependencies**: OpenAI API, cluster data
- **Files**: `ai/modules-ai.php`
- **Functionality**: Idea generation, content planning
- **Returns**: Generated content ideas
### 2.2 Runware API Functions
#### `igny8_runware_request()`
- **Purpose**: Make Runware API requests
- **Dependencies**: Runware API, authentication
- **Files**: `ai/runware-api.php`
- **Functionality**: API communication, image generation
- **Returns**: API response data
#### `igny8_generate_images_for_post()`
- **Purpose**: Generate images for WordPress posts
- **Dependencies**: Runware API, post data
- **Files**: `ai/modules-ai.php`
- **Functionality**: Image generation, post integration
- **Returns**: Generated images
#### `igny8_generate_images_for_content_idea()`
- **Purpose**: Generate images for content ideas
- **Dependencies**: Runware API, content idea data
- **Files**: `ai/modules-ai.php`
- **Functionality**: Image generation, idea integration
- **Returns**: Generated images
### 2.3 AI Integration Functions
#### `igny8_get_ai_setting()`
- **Purpose**: Get AI-related settings
- **Dependencies**: WordPress options, AI configuration
- **Files**: `ai/integration.php`
- **Functionality**: Setting retrieval, configuration access
- **Returns**: AI setting value
#### `igny8_set_ai_setting()`
- **Purpose**: Set AI-related settings
- **Dependencies**: WordPress options, AI configuration
- **Files**: `ai/integration.php`
- **Functionality**: Setting storage, configuration update
- **Returns**: Boolean setting status
#### `igny8_validate_ai_response()`
- **Purpose**: Validate AI API responses
- **Dependencies**: AI APIs, response data
- **Files**: `ai/integration.php`
- **Functionality**: Response validation, error handling
- **Returns**: Boolean validation status
---
## 3. WORKFLOW AUTOMATION FUNCTIONS
### 3.1 Sync Functions
#### `igny8_sync_post_meta_data()`
- **Purpose**: Synchronize post metadata
- **Dependencies**: WordPress post meta, database
- **Files**: `flows/sync-functions.php`
- **Functionality**: Meta synchronization, data consistency
- **Returns**: Boolean sync status
#### `igny8_sync_post_meta_ajax()`
- **Purpose**: AJAX post meta synchronization
- **Dependencies**: WordPress AJAX, post meta
- **Files**: `flows/sync-ajax.php`
- **Functionality**: AJAX sync, real-time updates
- **Returns**: JSON response
#### `igny8_handle_keyword_cluster_update()`
- **Purpose**: Handle keyword cluster updates
- **Dependencies**: Database, cluster data
- **Files**: `flows/sync-functions.php`
- **Functionality**: Cluster update processing
- **Returns**: Boolean update status
### 3.2 Image Injection Functions
#### `igny8_inject_responsive_images_separate()`
- **Purpose**: Inject responsive images into posts
- **Dependencies**: WordPress posts, image data
- **Files**: `flows/image-injection-responsive.php`
- **Functionality**: Image injection, responsive setup
- **Returns**: Boolean injection status
#### `igny8_process_image_injection()`
- **Purpose**: Process image injection workflow
- **Dependencies**: Post data, image data
- **Files**: `flows/image-injection-responsive.php`
- **Functionality**: Injection processing, workflow management
- **Returns**: Boolean processing status
### 3.3 AJAX Functions
#### `igny8_ajax_bulk_publish_drafts()`
- **Purpose**: Bulk publish drafts via AJAX
- **Dependencies**: WordPress AJAX, post data
- **Files**: `core/admin/ajax.php`
- **Functionality**: Bulk publishing, progress tracking
- **Returns**: JSON response
#### `igny8_ajax_sync_post_meta()`
- **Purpose**: AJAX post meta synchronization
- **Dependencies**: WordPress AJAX, post meta
- **Files**: `flows/sync-ajax.php`
- **Functionality**: Real-time sync, data updates
- **Returns**: JSON response
---
## 4. MODULE MANAGEMENT FUNCTIONS
### 4.1 Module Manager Functions
#### `igny8_is_module_enabled()`
- **Purpose**: Check if module is enabled
- **Dependencies**: Module manager, module data
- **Files**: `core/admin/module-manager-class.php`
- **Functionality**: Module status checking
- **Returns**: Boolean enabled status
#### `igny8_get_enabled_modules()`
- **Purpose**: Get all enabled modules
- **Dependencies**: Module manager, module data
- **Files**: `core/admin/module-manager-class.php`
- **Functionality**: Module listing, status filtering
- **Returns**: Array of enabled modules
#### `igny8_register_module()`
- **Purpose**: Register new module
- **Dependencies**: Module manager, module data
- **Files**: `core/admin/module-manager-class.php`
- **Functionality**: Module registration, configuration
- **Returns**: Boolean registration status
#### `igny8_get_module_config()`
- **Purpose**: Get module configuration
- **Dependencies**: Module manager, module data
- **Files**: `core/admin/module-manager-class.php`
- **Functionality**: Configuration retrieval
- **Returns**: Module configuration array
### 4.2 Module Initialization Functions
#### `igny8_init_modules()`
- **Purpose**: Initialize all modules
- **Dependencies**: Module manager, module data
- **Files**: `core/admin/module-manager-class.php`
- **Functionality**: Module initialization, setup
- **Returns**: Boolean initialization status
#### `igny8_load_module()`
- **Purpose**: Load specific module
- **Dependencies**: Module manager, module files
- **Files**: `core/admin/module-manager-class.php`
- **Functionality**: Module loading, file inclusion
- **Returns**: Boolean load status
---
## 5. CRON AUTOMATION FUNCTIONS
### 5.1 Master Dispatcher Functions
#### `igny8_master_dispatcher_run()`
- **Purpose**: Run master CRON dispatcher
- **Dependencies**: WordPress CRON, automation data
- **Files**: `core/cron/igny8-cron-master-dispatcher.php`
- **Functionality**: CRON orchestration, task management
- **Returns**: Boolean execution status
#### `igny8_get_defined_cron_jobs()`
- **Purpose**: Get all defined CRON jobs
- **Dependencies**: CRON configuration, job data
- **Files**: `core/cron/igny8-cron-master-dispatcher.php`
- **Functionality**: Job listing, configuration retrieval
- **Returns**: Array of CRON jobs
#### `igny8_check_cron_health()`
- **Purpose**: Check CRON system health
- **Dependencies**: CRON system, health data
- **Files**: `core/cron/igny8-cron-master-dispatcher.php`
- **Functionality**: Health monitoring, status checking
- **Returns**: Health status array
### 5.2 CRON Handler Functions
#### `igny8_process_ai_queue_cron_handler()`
- **Purpose**: Process AI queue via CRON
- **Dependencies**: AI queue, CRON system
- **Files**: `core/cron/igny8-cron-handlers.php`
- **Functionality**: Queue processing, AI task execution
- **Returns**: Boolean processing status
#### `igny8_auto_cluster_cron_handler()`
- **Purpose**: Auto cluster keywords via CRON
- **Dependencies**: Keyword data, clustering system
- **Files**: `core/cron/igny8-cron-handlers.php`
- **Functionality**: Automated clustering, keyword processing
- **Returns**: Boolean clustering status
#### `igny8_auto_generate_ideas_cron_handler()`
- **Purpose**: Auto generate ideas via CRON
- **Dependencies**: Cluster data, idea generation
- **Files**: `core/cron/igny8-cron-handlers.php`
- **Functionality**: Automated idea generation
- **Returns**: Boolean generation status
#### `igny8_auto_queue_cron_handler()`
- **Purpose**: Auto queue tasks via CRON
- **Dependencies**: Task data, queue system
- **Files**: `core/cron/igny8-cron-handlers.php`
- **Functionality**: Automated task queuing
- **Returns**: Boolean queuing status
#### `igny8_auto_generate_content_cron_handler()`
- **Purpose**: Auto generate content via CRON
- **Dependencies**: Content data, generation system
- **Files**: `core/cron/igny8-cron-handlers.php`
- **Functionality**: Automated content generation
- **Returns**: Boolean generation status
#### `igny8_auto_publish_drafts_cron_handler()`
- **Purpose**: Auto publish drafts via CRON
- **Dependencies**: Draft data, publishing system
- **Files**: `core/cron/igny8-cron-handlers.php`
- **Functionality**: Automated draft publishing
- **Returns**: Boolean publishing status
#### `igny8_auto_optimizer_cron_handler()`
- **Purpose**: Auto optimize content via CRON
- **Dependencies**: Content data, optimization system
- **Files**: `core/cron/igny8-cron-handlers.php`
- **Functionality**: Automated content optimization
- **Returns**: Boolean optimization status
#### `igny8_auto_recalc_cron_handler()`
- **Purpose**: Auto recalculate metrics via CRON
- **Dependencies**: Metric data, calculation system
- **Files**: `core/cron/igny8-cron-handlers.php`
- **Functionality**: Automated metric recalculation
- **Returns**: Boolean calculation status
#### `igny8_health_check_cron_handler()`
- **Purpose**: Health check via CRON
- **Dependencies**: System data, health monitoring
- **Files**: `core/cron/igny8-cron-handlers.php`
- **Functionality**: System health monitoring
- **Returns**: Boolean health status
#### `igny8_auto_generate_images_cron_handler()`
- **Purpose**: Auto generate images via CRON
- **Dependencies**: Image data, generation system
- **Files**: `core/cron/igny8-cron-handlers.php`
- **Functionality**: Automated image generation
- **Returns**: Boolean generation status
### 5.3 CRON Utility Functions
#### `igny8_safe_create_term()`
- **Purpose**: Safely create taxonomy terms
- **Dependencies**: WordPress taxonomy, term data
- **Files**: `core/cron/igny8-cron-handlers.php`
- **Functionality**: Term creation, error handling
- **Returns**: Term ID or false
#### `igny8_safe_create_cluster_term()`
- **Purpose**: Safely create cluster terms
- **Dependencies**: WordPress taxonomy, cluster data
- **Files**: `core/cron/igny8-cron-handlers.php`
- **Functionality**: Cluster term creation, error handling
- **Returns**: Term ID or false
---
## 6. ADMIN INTERFACE FUNCTIONS
### 6.1 Table Rendering Functions
#### `igny8_render_table()`
- **Purpose**: Render data tables
- **Dependencies**: Table data, configuration
- **Files**: `modules/components/table-tpl.php`
- **Functionality**: Table rendering, data display
- **Returns**: HTML table output
#### `igny8_render_filters()`
- **Purpose**: Render table filters
- **Dependencies**: Filter data, configuration
- **Files**: `modules/components/filters-tpl.php`
- **Functionality**: Filter rendering, user interface
- **Returns**: HTML filter output
#### `igny8_render_pagination()`
- **Purpose**: Render table pagination
- **Dependencies**: Pagination data, configuration
- **Files**: `modules/components/pagination-tpl.php`
- **Functionality**: Pagination rendering, navigation
- **Returns**: HTML pagination output
#### `igny8_render_table_actions()`
- **Purpose**: Render table actions
- **Dependencies**: Action data, configuration
- **Files**: `modules/components/actions-tpl.php`
- **Functionality**: Action rendering, user interface
- **Returns**: HTML action output
### 6.2 Form Rendering Functions
#### `igny8_render_forms()`
- **Purpose**: Render forms
- **Dependencies**: Form data, configuration
- **Files**: `modules/components/forms-tpl.php`
- **Functionality**: Form rendering, user interface
- **Returns**: HTML form output
#### `igny8_render_import_modal()`
- **Purpose**: Render import modal
- **Dependencies**: Import data, configuration
- **Files**: `modules/components/import-modal-tpl.php`
- **Functionality**: Import modal rendering
- **Returns**: HTML modal output
#### `igny8_render_export_modal()`
- **Purpose**: Render export modal
- **Dependencies**: Export data, configuration
- **Files**: `modules/components/export-modal-tpl.php`
- **Functionality**: Export modal rendering
- **Returns**: HTML modal output
### 6.3 KPI Rendering Functions
#### `igny8_render_kpi()`
- **Purpose**: Render KPI displays
- **Dependencies**: KPI data, configuration
- **Files**: `modules/components/kpi-tpl.php`
- **Functionality**: KPI rendering, metrics display
- **Returns**: HTML KPI output
---
## 7. ANALYTICS & KPI FUNCTIONS
### 7.1 KPI Data Functions
#### `igny8_get_kpi_data_safe()`
- **Purpose**: Safely get KPI data
- **Dependencies**: Database, KPI configuration
- **Files**: `core/admin/global-helpers.php`
- **Functionality**: KPI data retrieval, error handling
- **Returns**: KPI data array
#### `igny8_calculate_kpi_metrics()`
- **Purpose**: Calculate KPI metrics
- **Dependencies**: Raw data, calculation algorithms
- **Files**: `core/admin/global-helpers.php`
- **Functionality**: Metric calculation, data processing
- **Returns**: Calculated metrics array
#### `igny8_track_metrics()`
- **Purpose**: Track performance metrics
- **Dependencies**: Performance data, tracking system
- **Files**: `core/admin/global-helpers.php`
- **Functionality**: Metric tracking, data storage
- **Returns**: Boolean tracking status
### 7.2 Analytics Functions
#### `igny8_analyze_performance()`
- **Purpose**: Analyze performance data
- **Dependencies**: Performance data, analysis algorithms
- **Files**: `core/admin/global-helpers.php`
- **Functionality**: Performance analysis, insights generation
- **Returns**: Analysis results array
#### `igny8_generate_reports()`
- **Purpose**: Generate analytics reports
- **Dependencies**: Analytics data, report templates
- **Files**: `core/admin/global-helpers.php`
- **Functionality**: Report generation, data visualization
- **Returns**: Generated reports
#### `igny8_visualize_data()`
- **Purpose**: Visualize analytics data
- **Dependencies**: Analytics data, visualization tools
- **Files**: `core/admin/global-helpers.php`
- **Functionality**: Data visualization, chart generation
- **Returns**: Visualized data
---
## 8. UTILITY FUNCTIONS
### 8.1 Helper Functions
#### `igny8_get_cluster_term_name()`
- **Purpose**: Get cluster term name
- **Dependencies**: WordPress taxonomy, term data
- **Files**: `core/admin/global-helpers.php`
- **Functionality**: Term name retrieval
- **Returns**: Term name string
#### `igny8_get_cluster_options()`
- **Purpose**: Get cluster options for dropdowns
- **Dependencies**: Cluster data, taxonomy
- **Files**: `core/admin/global-helpers.php`
- **Functionality**: Options generation, dropdown data
- **Returns**: Options array
#### `igny8_get_dynamic_table_config()`
- **Purpose**: Get dynamic table configuration
- **Dependencies**: Table data, configuration
- **Files**: `core/admin/global-helpers.php`
- **Functionality**: Configuration generation, table setup
- **Returns**: Table configuration array
#### `igny8_get_difficulty_range_name()`
- **Purpose**: Get difficulty range name
- **Dependencies**: Difficulty data, range configuration
- **Files**: `core/admin/global-helpers.php`
- **Functionality**: Range name retrieval
- **Returns**: Range name string
### 8.2 Logging Functions
#### `igny8_write_log()`
- **Purpose**: Write to log system
- **Dependencies**: Logging system, log data
- **Files**: `core/admin/global-helpers.php`
- **Functionality**: Log writing, error tracking
- **Returns**: Boolean log status
#### `igny8_log_error()`
- **Purpose**: Log errors
- **Dependencies**: Error data, logging system
- **Files**: `core/admin/global-helpers.php`
- **Functionality**: Error logging, debugging
- **Returns**: Boolean log status
#### `igny8_log_debug()`
- **Purpose**: Log debug information
- **Dependencies**: Debug data, logging system
- **Files**: `core/admin/global-helpers.php`
- **Functionality**: Debug logging, troubleshooting
- **Returns**: Boolean log status
### 8.3 Validation Functions
#### `igny8_validate_record()`
- **Purpose**: Validate database records
- **Dependencies**: Record data, validation rules
- **Files**: `core/admin/global-helpers.php`
- **Functionality**: Record validation, data integrity
- **Returns**: Boolean validation status
#### `igny8_validate_foreign_key()`
- **Purpose**: Validate foreign key relationships
- **Dependencies**: Key data, relationship rules
- **Files**: `core/admin/global-helpers.php`
- **Functionality**: Foreign key validation
- **Returns**: Boolean validation status
#### `igny8_sanitize_data()`
- **Purpose**: Sanitize input data
- **Dependencies**: Input data, sanitization rules
- **Files**: `core/admin/global-helpers.php`
- **Functionality**: Data sanitization, security
- **Returns**: Sanitized data
---
## 9. FRONTEND FUNCTIONS
### 9.1 JavaScript Functions
#### `igny8_init_table()`
- **Purpose**: Initialize data tables
- **Dependencies**: JavaScript, table data
- **Files**: `assets/js/core.js`
- **Functionality**: Table initialization, user interface
- **Returns**: Boolean initialization status
#### `igny8_handle_ajax()`
- **Purpose**: Handle AJAX requests
- **Dependencies**: JavaScript, AJAX data
- **Files**: `assets/js/core.js`
- **Functionality**: AJAX handling, data processing
- **Returns**: AJAX response
#### `igny8_process_image_queue()`
- **Purpose**: Process image generation queue
- **Dependencies**: JavaScript, image data
- **Files**: `assets/js/image-queue-processor.js`
- **Functionality**: Queue processing, progress tracking
- **Returns**: Boolean processing status
### 9.2 CSS Functions
#### `igny8_load_styles()`
- **Purpose**: Load CSS styles
- **Dependencies**: CSS files, styling data
- **Files**: `assets/css/core.css`
- **Functionality**: Style loading, visual presentation
- **Returns**: Boolean load status
#### `igny8_apply_responsive_design()`
- **Purpose**: Apply responsive design
- **Dependencies**: CSS, responsive data
- **Files**: `assets/css/core.css`
- **Functionality**: Responsive design application
- **Returns**: Boolean application status
---
## 10. INTEGRATION FUNCTIONS
### 10.1 WordPress Integration
#### `igny8_integrate_wordpress()`
- **Purpose**: Integrate with WordPress core
- **Dependencies**: WordPress hooks, core functions
- **Files**: `igny8.php`, `core/admin/init.php`
- **Functionality**: WordPress integration, hook registration
- **Returns**: Boolean integration status
#### `igny8_register_hooks()`
- **Purpose**: Register WordPress hooks
- **Dependencies**: WordPress hooks, action/filter system
- **Files**: `flows/sync-hooks.php`
- **Functionality**: Hook registration, event handling
- **Returns**: Boolean registration status
### 10.2 API Integration
#### `igny8_integrate_apis()`
- **Purpose**: Integrate with external APIs
- **Dependencies**: API credentials, HTTP client
- **Files**: `ai/integration.php`
- **Functionality**: API integration, data exchange
- **Returns**: Boolean integration status
#### `igny8_handle_api_errors()`
- **Purpose**: Handle API errors
- **Dependencies**: API responses, error handling
- **Files**: `ai/integration.php`
- **Functionality**: Error handling, recovery
- **Returns**: Boolean error handling status
---
## Function Dependencies Summary
### Core Dependencies
- **WordPress**: Hooks, actions, filters, database
- **PHP**: Core language functions, extensions
- **MySQL**: Database operations, queries
- **JavaScript**: Client-side functionality, AJAX
### External Dependencies
- **OpenAI API**: Content generation, AI processing
- **Runware API**: Image generation, visual content
- **cURL**: HTTP communication, API requests
- **JSON**: Data serialization, API communication
### Internal Dependencies
- **Database Layer**: Custom tables, queries, migrations
- **Module System**: Module management, configuration
- **CRON System**: Automation, scheduled tasks
- **Admin Interface**: User interface, data management
### File Structure Dependencies
- **Core Files**: Plugin initialization, system setup
- **Module Files**: Feature-specific implementations
- **AI Files**: AI service integrations
- **Workflow Files**: Process automation, data sync
- **Asset Files**: Frontend resources, styling
This comprehensive function reference covers all aspects of the Igny8 AI SEO Plugin's function library, providing detailed information about each function's purpose, dependencies, and implementation details.

View File

@@ -0,0 +1,476 @@
# Complete Image Generation Process Audit
## Summary
This document provides a comprehensive audit of the current image generation process in the Igny8 AI SEO plugin. The system has been reorganized with image generation functions moved to a dedicated module and improved queue processing.
### Files Involved
- **`ai/writer/images/image-generation.php`** - Main image generation functions (featured & article images)
- **`core/admin/ajax.php`** - AJAX handlers for image generation requests
- **`assets/js/image-queue-processor.js`** - JavaScript queue processing and UI
- **`ai/modules-ai.php`** - Content generation response handler (saves image prompts)
- **`ai/openai-api.php`** - OpenAI DALL-E API integration
- **`ai/runware-api.php`** - Runware API integration
### Functions and Purposes
- **`igny8_generate_featured_image_for_post()`** - Generates featured images using AI prompts
- **`igny8_generate_single_article_image()`** - Generates in-article images for specific sections
- **`igny8_ajax_ai_generate_single_image()`** - AJAX handler for single image generation
- **`igny8_add_inarticle_image_meta()`** - Saves image metadata to post meta
- **`igny8_format_image_prompts_for_ai()`** - Formats image prompts for AI content generation
- **`igny8_create_post_from_ai_response()`** - Saves image prompts from AI response
## Overview
This document provides a comprehensive audit of the image generation process in the Igny8 AI SEO plugin, covering every function, hook, setting, and data flow from button click to final image save.
## 1. Entry Point - Generate Images Button
### Location
- **File**: `modules/components/actions-tpl.php`
- **Lines**: 42-45
- **Button ID**: `writer_drafts_generate_images_btn`
- **HTML**:
```html
<button id="writer_drafts_generate_images_btn" class="igny8-btn igny8-btn-accent" disabled>
<span class="dashicons dashicons-format-image"></span> Generate Images
</button>
```
### JavaScript Event Handler
- **File**: `assets/js/image-queue-processor.js`
- **Function**: `processAIImageGenerationDrafts(postIds)`
- **Lines**: 7-84
- **Validation**: Max 10 posts, requires selection
## 2. JavaScript Processing Flow
### Main Processing Function
- **File**: `assets/js/image-queue-processor.js`
- **Function**: `processAIImageGenerationDrafts(postIds)`
- **Lines**: 7-84
### Settings Retrieved
```javascript
const desktopEnabled = window.IGNY8_PAGE?.imageSettings?.desktop_enabled || false;
const mobileEnabled = window.IGNY8_PAGE?.imageSettings?.mobile_enabled || false;
const maxInArticleImages = window.IGNY8_PAGE?.imageSettings?.max_in_article_images || 1;
```
### Image Calculation
- **Featured Images**: Always 1 per post
- **Desktop Images**: `maxInArticleImages` per post (if enabled)
- **Mobile Images**: `maxInArticleImages` per post (if enabled)
- **Total**: `postIds.length * imagesPerPost`
### Queue Processing
- **Sequential Processing**: One image at a time to avoid API limits
- **Progress Tracking**: Real-time progress updates with individual progress bars
- **Error Handling**: Continue processing on individual failures
- **Modal Display**: Shows progress for each image being generated
## 3. AJAX Handlers
### Primary Handler
- **File**: `core/admin/ajax.php`
- **Function**: `igny8_ajax_ai_generate_images_drafts()`
- **Lines**: 2913-3150
- **Hook**: `wp_ajax_igny8_ai_generate_images_drafts`
### Single Image Handler
- **File**: `core/admin/ajax.php`
- **Function**: `igny8_ajax_ai_generate_single_image()`
- **Lines**: 3283-3350
- **Hook**: `wp_ajax_igny8_ai_generate_single_image`
- **Process**:
1. Validates task ID and retrieves WordPress post ID
2. Calls appropriate generation function based on type
3. Saves image metadata to post meta
4. Returns success/error response
### Supporting Handlers
- **Image Counts**: `igny8_ajax_get_image_counts()` (Lines 2645-2809)
- **Single Image Queue**: `igny8_ajax_generate_single_image_queue()` (Lines 2814-2908)
- **Settings Save**: `igny8_ajax_save_image_settings()` (Lines 4408-4457)
- **Template Save**: `igny8_ajax_save_image_prompt_template()` (Lines 4461-4505)
## 4. Settings and Configuration
### WordPress Options
| **Option** | **Default** | **Purpose** |
|------------|-------------|-------------|
| `igny8_desktop_enabled` | `'1'` | Enable desktop image generation |
| `igny8_mobile_enabled` | `'0'` | Enable mobile image generation |
| `igny8_max_in_article_images` | `1` | Maximum in-article images per post |
| `igny8_image_type` | `'realistic'` | Image style type |
| `igny8_image_provider` | `'runware'` | AI provider for image generation |
| `igny8_image_format` | `'jpg'` | Output image format |
| `igny8_negative_prompt` | `'text, watermark, logo, overlay, title, caption, writing on walls, writing on objects, UI, infographic elements, post title'` | Negative prompt for image generation |
| `igny8_image_prompt_template` | `'Create a high-quality {image_type} image to use as a featured photo for a blog post titled "{post_title}". The image should visually represent the theme, mood, and subject implied by the image prompt: {image_prompt}. Focus on a realistic, well-composed scene that naturally communicates the topic without text or logos. Use balanced lighting, pleasing composition, and photographic detail suitable for lifestyle or editorial web content. Avoid adding any visible or readable text, brand names, or illustrative effects. **And make sure image is not blurry.**'` | Image prompt template |
### Settings Page
- **File**: `modules/settings/general-settings.php`
- **Lines**: 309-462
- **Form Fields**: Desktop/Mobile toggles, Max images dropdown, Image type, Provider, Format, Negative prompt, Template
## 5. Image Generation Functions
### Core Generation Functions
- **File**: `ai/writer/images/image-generation.php`
#### Featured Image Generation
- **Function**: `igny8_generate_featured_image_for_post($post_id, $image_size_type = 'featured')`
- **Lines**: 24-200
- **Process**:
1. Get featured image prompt from `_igny8_featured_image_prompt`
2. Get image generation settings from WordPress options
3. Build final prompt using template with placeholders
4. Call AI provider (Runware/OpenAI) with appropriate dimensions
5. Download image from AI provider URL
6. Upload via `media_handle_sideload()`
7. Set as featured image with `set_post_thumbnail()`
8. Generate attachment metadata
#### In-Article Image Generation
- **Function**: `igny8_generate_single_article_image($post_id, $device_type = 'desktop', $index = 1)`
- **Lines**: 202-400
- **Process**:
1. Get article image prompts from `_igny8_article_images_data`
2. Extract prompt for specific index (`prompt-img-1`, `prompt-img-2`, etc.)
3. Get image generation settings from WordPress options
4. Build final prompt using template with placeholders
5. Call AI provider with device-specific dimensions
6. Download image from AI provider URL
7. Upload via `media_handle_sideload()`
8. Return attachment ID and metadata
### Supporting Functions
- **Image Dimensions**: `igny8_get_image_dimensions($size_preset, $provider)` (Lines 1526-1558 in ai/modules-ai.php)
- **Safe Quantity**: `igny8_calculate_safe_image_quantity($idea_data, $max_in_article_images)` (Lines 918-933 in ai/modules-ai.php)
- **Image Meta**: `igny8_add_inarticle_image_meta($post_id, $attachment_id, $label, $device, $section)` (Lines 933-963 in ai/modules-ai.php)
## 6. AI Provider Integration
### Runware API Integration
- **File**: `ai/runware-api.php`
- **Function**: `igny8_runway_generate_image($prompt, $negative_prompt, $width, $height, $steps, $cfg_scale, $number_results, $output_format)`
- **API Endpoint**: `https://api.runware.ai/v1`
- **Authentication**: API key via `igny8_runware_api_key` option
- **Model**: `runware:97@1` (HiDream-I1 Full)
- **Request Format**:
```json
[
{
"taskType": "authentication",
"apiKey": "api_key_here"
},
{
"taskType": "imageInference",
"taskUUID": "uuid_here",
"positivePrompt": "prompt_text",
"negativePrompt": "negative_prompt",
"model": "runware:97@1",
"width": 1024,
"height": 1024,
"steps": 30,
"CFGScale": 7.5,
"numberResults": 1,
"outputFormat": "jpg"
}
]
```
- **Response Handling**: Extract `data[0].imageURL` from response
- **Error Handling**: Log API errors, return error messages
### OpenAI DALL-E Integration
- **File**: `ai/openai-api.php`
- **Function**: `igny8_call_openai_images($prompt, $api_key, $model, $size, $quality, $style)`
- **API Endpoint**: `https://api.openai.com/v1/images/generations`
- **Authentication**: API key via `igny8_api_key` option
- **Model**: `dall-e-3`
- **Request Format**:
```json
{
"model": "dall-e-3",
"prompt": "prompt_text",
"n": 1,
"size": "1024x1024",
"quality": "standard",
"style": "natural"
}
```
- **Response Handling**: Extract `data[0].url` from response
- **Error Handling**: Log API errors, return error messages
### Provider Detection and Selection
- **Settings**: `igny8_image_provider` option (`runware` or `openai`)
- **API Key Validation**: Check `igny8_runware_api_key` or `igny8_api_key`
- **Dynamic Selection**: Based on user settings in prompts page
- **Fallback Handling**: Return error if required API key not configured
### Image Dimensions by Provider
- **Runware Dimensions**:
- Featured: 1280x832
- Desktop: 1024x1024
- Mobile: 960x1280
- **OpenAI Dimensions**:
- Featured: 1024x1024
- Desktop: 1024x1024
- Mobile: 1024x1024
## 7. Data Storage and Meta Keys
### Post Meta Keys
| **Meta Key** | **Purpose** | **Data Type** |
|--------------|-------------|---------------|
| `_igny8_featured_image_prompt` | Featured image AI prompt | Text |
| `_igny8_article_images_data` | In-article image prompts | JSON Array |
| `_igny8_inarticle_images` | Generated image metadata | JSON Object |
### Image Prompts Data Structure
```json
[
{
"prompt-img-1": "A close-up of a neatly made bed showcasing a well-fitted duvet cover that enhances the aesthetic of the room."
},
{
"prompt-img-2": "An image of tools laid out for measuring a duvet insert, including a measuring tape, notepad, and a flat surface."
},
{
"prompt-img-3": "A detailed size chart displaying different duvet cover sizes alongside their corresponding duvet insert dimensions."
},
{
"prompt-img-4": "An infographic illustrating common mistakes when choosing duvet covers, highlighting shrinkage risks and misreading labels."
}
]
```
### Image Metadata Structure
```json
{
"desktop-1": {
"label": "desktop-1",
"attachment_id": 1825,
"url": "https://example.com/image.jpg",
"device": "desktop",
"section": 1
},
"mobile-1": {
"label": "mobile-1",
"attachment_id": 1826,
"url": "https://example.com/image2.jpg",
"device": "mobile",
"section": 1
}
}
```
## 8. Image Processing Workflow
### Step-by-Step Process
1. **Button Click**: User selects posts and clicks "Generate Images"
2. **Validation**: Check selection count (max 10), get settings
3. **Queue Building**: Build image queue with featured and in-article images
4. **Progress Modal**: Show progress with individual progress bars
5. **Sequential Processing**: Process each image individually
6. **Image Generation**: For each image:
- Get prompt from appropriate meta field
- Call AI provider with settings
- Download image from provider URL
- Upload to WordPress Media Library
- Save metadata to post meta
7. **Progress Updates**: Update UI with success/failure status
8. **Completion**: Show final results
### Error Handling
- **Download Failures**: Log errors, continue with next image
- **Upload Failures**: Log errors, return failure status
- **API Failures**: Log errors, return failure status
- **Progress Tracking**: Update modal with success/failure counts
- **JSON Parsing**: Handle HTML content in prompts with `wp_strip_all_tags()`
## 9. Content Generation Integration
### Image Prompts in Content Generation
- **File**: `ai/modules-ai.php`
- **Function**: `igny8_create_post_from_ai_response($ai_response)`
- **Lines**: 1119-1462
- **Process**:
1. AI generates content with image prompts
2. Featured image prompt saved to `_igny8_featured_image_prompt`
3. In-article image prompts saved to `_igny8_article_images_data`
4. HTML tags stripped from prompts using `wp_strip_all_tags()`
5. JSON structure validated and saved
### Prompt Template Processing
- **Template**: Uses `{image_type}`, `{post_title}`, `{image_prompt}` placeholders
- **Replacement**: Dynamic replacement with actual values
- **Settings Integration**: Uses all settings from prompts page
## 10. Image Generation Queue Mechanism
### Queue Processing
- **File**: `assets/js/image-queue-processor.js`
- **Function**: `processAIImageGenerationDrafts(postIds)`
- **Lines**: 7-84
- **Process**: Sequential image generation with progress tracking
### Queue Features
- **Sequential Processing**: One image at a time to avoid API limits
- **Progress Tracking**: Real-time progress updates with individual bars
- **Error Handling**: Continue processing on individual failures
- **Batch Management**: Handle multiple posts with image counts
- **Modal Display**: Shows detailed progress for each image
### Queue Handler
- **File**: `core/admin/ajax.php`
- **Function**: `igny8_ajax_ai_generate_single_image()`
- **Lines**: 3283-3350
- **Hook**: `wp_ajax_igny8_ai_generate_single_image`
## 11. Debug and Logging
### Debug Functions
- **File**: `debug/module-debug.php`
- **Lines**: 1185-1221 (HTML), 1447-1613 (JavaScript)
- **Features**:
- Image generation logs interface
- Refresh/clear buttons (`refresh-image-gen`, `clear-image-gen`)
- Real-time event display (`image-gen-events`)
- Status messages (`image-gen-message`, `image-gen-details`)
- Global debug function (`window.addImageGenDebugLog`)
### Comprehensive Logging System
- **Event-Based Logging**: `IMAGE_GEN_EVENT_1` through `IMAGE_GEN_EVENT_9`
- **Debug Events Array**: `$debug_events[]` for detailed tracking
- **Error Logging**: `error_log()` with specific prefixes
- **AI Event Logging**: `igny8_log_ai_event()` for AI interactions
### Logging Points
- **AJAX Entry**: `error_log('Igny8: AJAX HANDLER CALLED - igny8_ajax_ai_generate_images_drafts')`
- **Task Validation**: `error_log('Igny8: IMAGE_GEN_EVENT_3 - Task IDs validated')`
- **Post Retrieval**: `error_log('Igny8: IMAGE_GEN_EVENT_4 - WordPress post IDs retrieved')`
- **Image Prompts**: `error_log('Igny8: IMAGE_GEN_EVENT_5 - Image prompts loaded')`
- **Featured Generation**: `error_log('Igny8: IMAGE_GEN_EVENT_6 - Featured image generation initiated')`
- **API Requests**: `error_log('Igny8: IMAGE_GEN_EVENT_7 - API request sent')`
- **Image URLs**: `error_log('Igny8: IMAGE_GEN_EVENT_8 - Image URL received')`
- **WordPress Save**: `error_log('Igny8: IMAGE_GEN_EVENT_9 - Saving image to WordPress')`
- **Success/Failure**: `error_log('Igny8: IMAGE_GEN_EVENT_9_SUCCESS/ERROR')`
## 12. File Dependencies
### Core Files
- `ai/writer/images/image-generation.php` - Main image generation functions
- `core/admin/ajax.php` - AJAX handlers
- `assets/js/image-queue-processor.js` - Queue processing JavaScript
- `ai/modules-ai.php` - Content generation response handler
- `ai/openai-api.php` - OpenAI DALL-E API integration
- `ai/runware-api.php` - Runware API integration
### Settings Files
- `modules/settings/general-settings.php` - Main image generation settings page
- `modules/thinker/prompts.php` - Image prompt templates only
- `modules/thinker/image-testing.php` - Image testing interface
- `modules/modules-pages/writer.php` - Settings localization
### Debug Files
- `debug/module-debug.php` - Debug interface
## 13. Hooks and Actions
### WordPress Hooks
- `wp_ajax_igny8_ai_generate_images_drafts` - Main generation handler
- `wp_ajax_igny8_ai_generate_single_image` - Single image handler
- `wp_ajax_igny8_generate_single_image_queue` - Queue handler
- `wp_ajax_igny8_get_image_counts` - Image count preview
- `wp_ajax_igny8_save_image_settings` - Settings save
- `wp_ajax_igny8_save_image_prompt_template` - Template save
- `wp_ajax_igny8_reset_image_prompt_template` - Template reset
### Internal Hooks
- `transition_post_status` - Post status changes
- `save_post` - Post save events
## 14. Security Considerations
### Nonce Verification
- All AJAX handlers verify nonces
- Settings forms use proper nonce fields
- User capability checks for admin functions
### Data Sanitization
- All input data sanitized with `sanitize_text_field()`
- File uploads handled via WordPress functions
- Image URLs validated before processing
- HTML tags stripped from prompts using `wp_strip_all_tags()`
## 15. Performance Considerations
### Sequential Processing
- Images generated one at a time to avoid API limits
- Progress tracking for user feedback
- Error handling to continue processing
### Media Library Integration
- Proper WordPress Media Library registration
- Automatic thumbnail generation
- Metadata attachment for SEO
## 16. Complete Function Reference
### AJAX Handlers
- `igny8_ajax_ai_generate_images_drafts()` - Main generation
- `igny8_ajax_ai_generate_single_image()` - Single image
- `igny8_ajax_generate_single_image_queue()` - Queue processing
- `igny8_ajax_get_image_counts()` - Count preview
- `igny8_ajax_save_image_settings()` - Settings save
- `igny8_ajax_save_image_prompt_template()` - Template save
- `igny8_ajax_reset_image_prompt_template()` - Template reset
### Generation Functions
- `igny8_generate_featured_image_for_post()` - Featured image
- `igny8_generate_single_article_image()` - In-article image
- `igny8_get_image_dimensions()` - Size calculation
- `igny8_calculate_safe_image_quantity()` - Quantity safety
### Content Functions
- `igny8_add_inarticle_image_meta()` - Image metadata saving
- `igny8_format_image_prompts_for_ai()` - Prompt formatting
- `igny8_create_post_from_ai_response()` - Content generation response handler
### Queue Processing Functions
- `processAIImageGenerationDrafts()` - Main queue processor
- `generateAllImagesForPost()` - Single post processing
- `updateProgressModal()` - Progress updates
### API Functions
- `igny8_runway_generate_image()` - Runware API integration
- `igny8_call_openai_images()` - OpenAI DALL-E API integration
### Settings Functions
- `igny8_ajax_save_image_settings()` - Settings save
- `igny8_ajax_save_image_prompt_template()` - Template save
- `igny8_ajax_reset_image_prompt_template()` - Template reset
## 17. Recent Changes and Improvements
### Code Reorganization
- **Image generation functions moved** from `ai/modules-ai.php` to `ai/writer/images/image-generation.php`
- **Dedicated module** for image generation functionality
- **Improved separation of concerns** between content generation and image generation
### Enhanced Error Handling
- **JSON parsing improvements** with HTML tag stripping
- **Better error messages** for debugging
- **Graceful fallbacks** for API failures
### Improved Queue Processing
- **Individual progress bars** for each image
- **Better error tracking** and reporting
- **Enhanced user feedback** during processing
### Settings Integration
- **Dynamic settings** from prompts page
- **Template-based prompts** with placeholder replacement
- **Provider selection** with appropriate API key validation
This audit covers every aspect of the current image generation process from initial button click to final image storage and metadata saving, including the complete queue mechanism, settings integration, and content generation workflow.

View File

@@ -0,0 +1,723 @@
# Igny8 AI SEO Plugin - Complete Workflows Documentation
## Summary Table
| Workflow | Process Steps | Functions Involved | Dependencies | Files Involved |
|----------|---------------|-------------------|--------------|----------------|
| **Content Planning** | Keyword Research → Clustering → Ideas → Queue | `igny8_research_keywords()`, `igny8_ai_cluster_keywords()`, `igny8_ai_generate_ideas()`, `igny8_queue_ideas_to_writer()` | OpenAI API, Database, WordPress | `modules/modules-pages/planner.php`, `ai/modules-ai.php`, `flows/sync-functions.php` |
| **Content Creation** | Task Creation → AI Generation → Draft Review → Publishing | `igny8_create_task()`, `igny8_generate_content()`, `igny8_review_draft()`, `igny8_publish_content()` | AI APIs, WordPress, Database | `modules/modules-pages/writer.php`, `ai/modules-ai.php`, `flows/sync-functions.php` |
| **SEO Optimization** | Content Analysis → Suggestions → Implementation → Monitoring | `igny8_analyze_content()`, `igny8_generate_suggestions()`, `igny8_implement_optimizations()`, `igny8_monitor_performance()` | SEO APIs, Database | `modules/modules-pages/optimizer.php`, `ai/modules-ai.php` |
| **Link Building** | Campaign Planning → Outreach → Tracking → Analysis | `igny8_plan_campaign()`, `igny8_manage_outreach()`, `igny8_track_backlinks()`, `igny8_analyze_results()` | External APIs, Database | `modules/modules-pages/linker.php`, `flows/sync-functions.php` |
| **Content Personalization** | Field Detection → Content Rewriting → Frontend Display → Analytics | `igny8_detect_fields()`, `igny8_rewrite_content()`, `igny8_display_personalized()`, `igny8_track_personalization()` | OpenAI API, Frontend | `modules/modules-pages/personalize/`, `ai/integration.php` |
| **Automation Workflows** | Schedule Setup → Task Execution → Monitoring → Optimization | `igny8_schedule_tasks()`, `igny8_execute_automation()`, `igny8_monitor_automation()`, `igny8_optimize_automation()` | WordPress CRON, Database | `core/cron/`, `core/pages/settings/schedules.php` |
| **Analytics & Reporting** | Data Collection → Analysis → Visualization → Reporting | `igny8_collect_metrics()`, `igny8_analyze_data()`, `igny8_visualize_results()`, `igny8_generate_reports()` | Database, WordPress | `core/admin/`, `modules/config/kpi-config.php` |
---
## 1. CONTENT PLANNING WORKFLOW
### 1.1 Keyword Research Process
#### Step 1: Keyword Import
- **Function**: `igny8_import_keywords()`
- **Process**: CSV file upload and validation
- **Dependencies**: File upload system, data validation
- **Files**: `modules/components/import-modal-tpl.php`, `flows/sync-functions.php`
- **Validation**: Duplicate detection, data format validation
- **Output**: Validated keyword data in database
#### Step 2: Keyword Analysis
- **Function**: `igny8_analyze_keywords()`
- **Process**: Keyword metrics calculation and categorization
- **Dependencies**: External SEO APIs, database queries
- **Files**: `ai/modules-ai.php`, `core/db/db.php`
- **Analysis**: Volume, difficulty, competition scoring
- **Output**: Analyzed keyword data with metrics
#### Step 3: Keyword Categorization
- **Function**: `igny8_categorize_keywords()`
- **Process**: Primary/secondary keyword classification
- **Dependencies**: AI analysis, keyword relationships
- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php`
- **Classification**: Primary, secondary, long-tail categorization
- **Output**: Categorized keyword data
### 1.2 AI Clustering Process
#### Step 1: Cluster Analysis
- **Function**: `igny8_ai_cluster_keywords()`
- **Process**: AI-powered semantic clustering
- **Dependencies**: OpenAI API, keyword data
- **Files**: `ai/modules-ai.php`, `ai/openai-api.php`
- **Analysis**: Semantic similarity analysis
- **Output**: Keyword cluster assignments
#### Step 2: Cluster Optimization
- **Function**: `igny8_optimize_clusters()`
- **Process**: Cluster refinement and optimization
- **Dependencies**: Cluster data, AI analysis
- **Files**: `flows/sync-functions.php`, `ai/modules-ai.php`
- **Optimization**: Cluster size, keyword distribution
- **Output**: Optimized cluster structure
#### Step 3: Cluster Metrics
- **Function**: `igny8_calculate_cluster_metrics()`
- **Process**: Cluster performance calculation
- **Dependencies**: Cluster data, keyword metrics
- **Files**: `core/admin/global-helpers.php`, `flows/sync-functions.php`
- **Metrics**: Volume aggregation, keyword count
- **Output**: Cluster performance metrics
### 1.3 Content Idea Generation
#### Step 1: Idea Generation
- **Function**: `igny8_ai_generate_ideas()`
- **Process**: AI-powered content idea creation
- **Dependencies**: OpenAI API, cluster data
- **Files**: `ai/modules-ai.php`, `ai/openai-api.php`
- **Generation**: Content ideas based on clusters
- **Output**: Generated content ideas
#### Step 2: Idea Categorization
- **Function**: `igny8_categorize_ideas()`
- **Process**: Content type and priority classification
- **Dependencies**: AI analysis, content templates
- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php`
- **Categorization**: Content type, priority scoring
- **Output**: Categorized content ideas
#### Step 3: Idea Queue Management
- **Function**: `igny8_queue_ideas_to_writer()`
- **Process**: Idea prioritization and queue management
- **Dependencies**: Idea data, writer module
- **Files**: `flows/sync-functions.php`, `modules/modules-pages/writer.php`
- **Queue**: Priority-based idea queuing
- **Output**: Queued content ideas for writer
---
## 2. CONTENT CREATION WORKFLOW
### 2.1 Task Creation Process
#### Step 1: Task Generation
- **Function**: `igny8_create_writing_task()`
- **Process**: Content task creation from ideas
- **Dependencies**: Content ideas, writer settings
- **Files**: `modules/modules-pages/writer.php`, `flows/sync-functions.php`
- **Creation**: Task assignment and configuration
- **Output**: Writing tasks in database
#### Step 2: Task Prioritization
- **Function**: `igny8_prioritize_tasks()`
- **Process**: Task priority calculation and ordering
- **Dependencies**: Task data, priority algorithms
- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php`
- **Prioritization**: Priority scoring and ordering
- **Output**: Prioritized task queue
#### Step 3: Task Assignment
- **Function**: `igny8_assign_tasks()`
- **Process**: Task assignment to writers or AI
- **Dependencies**: Task data, user preferences
- **Files**: `modules/modules-pages/writer.php`, `flows/sync-functions.php`
- **Assignment**: Writer or AI assignment
- **Output**: Assigned writing tasks
### 2.2 AI Content Generation
#### Step 1: Content Generation
- **Function**: `igny8_generate_content()`
- **Process**: AI-powered content creation
- **Dependencies**: OpenAI API, task data
- **Files**: `ai/modules-ai.php`, `ai/openai-api.php`
- **Generation**: AI content creation
- **Output**: Generated content drafts
#### Step 2: Content Optimization
- **Function**: `igny8_optimize_content()`
- **Process**: SEO and readability optimization
- **Dependencies**: AI analysis, SEO rules
- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php`
- **Optimization**: SEO, readability improvements
- **Output**: Optimized content
#### Step 3: Content Validation
- **Function**: `igny8_validate_content()`
- **Process**: Content quality and compliance checking
- **Dependencies**: Content data, quality rules
- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php`
- **Validation**: Quality, compliance checks
- **Output**: Validated content
### 2.3 Draft Management
#### Step 1: Draft Creation
- **Function**: `igny8_create_draft()`
- **Process**: WordPress draft post creation
- **Dependencies**: WordPress, content data
- **Files**: `flows/sync-functions.php`, `core/db/db.php`
- **Creation**: WordPress draft creation
- **Output**: Draft posts in WordPress
#### Step 2: Draft Review
- **Function**: `igny8_review_draft()`
- **Process**: Content review and editing interface
- **Dependencies**: WordPress admin, draft data
- **Files**: `modules/modules-pages/writer.php`, `core/admin/meta-boxes.php`
- **Review**: Content review interface
- **Output**: Reviewed draft content
#### Step 3: Draft Optimization
- **Function**: `igny8_optimize_draft()`
- **Process**: Draft content optimization
- **Dependencies**: AI analysis, optimization rules
- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php`
- **Optimization**: Content improvement
- **Output**: Optimized draft content
### 2.4 Publishing Process
#### Step 1: Publishing Preparation
- **Function**: `igny8_prepare_publishing()`
- **Process**: Pre-publication checks and preparation
- **Dependencies**: Draft data, publishing rules
- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php`
- **Preparation**: Pre-publication validation
- **Output**: Publishing-ready content
#### Step 2: Content Publishing
- **Function**: `igny8_publish_content()`
- **Process**: WordPress post publication
- **Dependencies**: WordPress, draft data
- **Files**: `flows/sync-functions.php`, `core/db/db.php`
- **Publishing**: WordPress post publication
- **Output**: Published content
#### Step 3: Post-Publishing
- **Function**: `igny8_post_publish_actions()`
- **Process**: Post-publication tasks and monitoring
- **Dependencies**: Published content, monitoring systems
- **Files**: `flows/sync-functions.php`, `flows/image-injection-responsive.php`
- **Actions**: Image injection, monitoring setup
- **Output**: Fully published and monitored content
---
## 3. SEO OPTIMIZATION WORKFLOW
### 3.1 Content Analysis Process
#### Step 1: SEO Audit
- **Function**: `igny8_audit_content()`
- **Process**: Comprehensive SEO analysis
- **Dependencies**: Content data, SEO rules
- **Files**: `modules/modules-pages/optimizer.php`, `ai/modules-ai.php`
- **Analysis**: SEO factor analysis
- **Output**: SEO audit results
#### Step 2: Keyword Analysis
- **Function**: `igny8_analyze_keywords()`
- **Process**: Keyword usage and optimization analysis
- **Dependencies**: Content data, keyword data
- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php`
- **Analysis**: Keyword density, placement analysis
- **Output**: Keyword optimization recommendations
#### Step 3: Technical SEO
- **Function**: `igny8_analyze_technical_seo()`
- **Process**: Technical SEO factor analysis
- **Dependencies**: Content data, technical rules
- **Files**: `modules/modules-pages/optimizer.php`, `core/admin/global-helpers.php`
- **Analysis**: Technical SEO factors
- **Output**: Technical SEO recommendations
### 3.2 Optimization Suggestions
#### Step 1: Suggestion Generation
- **Function**: `igny8_generate_suggestions()`
- **Process**: AI-powered optimization suggestions
- **Dependencies**: AI analysis, content data
- **Files**: `ai/modules-ai.php`, `ai/openai-api.php`
- **Generation**: AI optimization suggestions
- **Output**: Optimization recommendations
#### Step 2: Suggestion Prioritization
- **Function**: `igny8_prioritize_suggestions()`
- **Process**: Suggestion priority and impact analysis
- **Dependencies**: Suggestion data, impact algorithms
- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php`
- **Prioritization**: Impact-based prioritization
- **Output**: Prioritized suggestions
#### Step 3: Implementation Planning
- **Function**: `igny8_plan_implementation()`
- **Process**: Implementation strategy development
- **Dependencies**: Suggestions, content data
- **Files**: `modules/modules-pages/optimizer.php`, `flows/sync-functions.php`
- **Planning**: Implementation strategy
- **Output**: Implementation plan
### 3.3 Performance Monitoring
#### Step 1: Metrics Collection
- **Function**: `igny8_collect_metrics()`
- **Process**: Performance data collection
- **Dependencies**: Analytics APIs, content data
- **Files**: `core/admin/global-helpers.php`, `modules/config/kpi-config.php`
- **Collection**: Performance data gathering
- **Output**: Performance metrics
#### Step 2: Performance Analysis
- **Function**: `igny8_analyze_performance()`
- **Process**: Performance trend and pattern analysis
- **Dependencies**: Metrics data, analysis algorithms
- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php`
- **Analysis**: Performance trend analysis
- **Output**: Performance insights
#### Step 3: Optimization Adjustment
- **Function**: `igny8_adjust_optimization()`
- **Process**: Performance-based optimization adjustments
- **Dependencies**: Performance data, optimization rules
- **Files**: `flows/sync-functions.php`, `ai/modules-ai.php`
- **Adjustment**: Optimization fine-tuning
- **Output**: Adjusted optimization strategy
---
## 4. LINK BUILDING WORKFLOW
### 4.1 Campaign Planning
#### Step 1: Campaign Strategy
- **Function**: `igny8_plan_campaign()`
- **Process**: Link building campaign strategy development
- **Dependencies**: Content data, target analysis
- **Files**: `modules/modules-pages/linker.php`, `ai/modules-ai.php`
- **Planning**: Campaign strategy development
- **Output**: Campaign strategy
#### Step 2: Target Identification
- **Function**: `igny8_identify_targets()`
- **Process**: High-value link target identification
- **Dependencies**: Content data, authority analysis
- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php`
- **Identification**: Target site identification
- **Output**: Link building targets
#### Step 3: Outreach Planning
- **Function**: `igny8_plan_outreach()`
- **Process**: Outreach strategy and message development
- **Dependencies**: Target data, content data
- **Files**: `modules/modules-pages/linker.php`, `flows/sync-functions.php`
- **Planning**: Outreach strategy
- **Output**: Outreach plan
### 4.2 Outreach Management
#### Step 1: Outreach Execution
- **Function**: `igny8_execute_outreach()`
- **Process**: Automated outreach campaign execution
- **Dependencies**: Outreach data, communication systems
- **Files**: `flows/sync-functions.php`, `modules/modules-pages/linker.php`
- **Execution**: Outreach campaign execution
- **Output**: Outreach activities
#### Step 2: Follow-up Management
- **Function**: `igny8_manage_followups()`
- **Process**: Follow-up communication management
- **Dependencies**: Outreach data, follow-up rules
- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php`
- **Management**: Follow-up communication
- **Output**: Follow-up activities
#### Step 3: Relationship Building
- **Function**: `igny8_build_relationships()`
- **Process**: Long-term relationship development
- **Dependencies**: Contact data, relationship rules
- **Files**: `modules/modules-pages/linker.php`, `flows/sync-functions.php`
- **Building**: Relationship development
- **Output**: Established relationships
### 4.3 Backlink Tracking
#### Step 1: Backlink Discovery
- **Function**: `igny8_discover_backlinks()`
- **Process**: Automated backlink detection and tracking
- **Dependencies**: External APIs, monitoring systems
- **Files**: `flows/sync-functions.php`, `core/db/db.php`
- **Discovery**: Backlink detection
- **Output**: Discovered backlinks
#### Step 2: Backlink Analysis
- **Function**: `igny8_analyze_backlinks()`
- **Process**: Backlink quality and authority analysis
- **Dependencies**: Backlink data, authority metrics
- **Files**: `ai/modules-ai.php`, `core/admin/global-helpers.php`
- **Analysis**: Backlink quality analysis
- **Output**: Backlink analysis results
#### Step 3: Performance Tracking
- **Function**: `igny8_track_backlink_performance()`
- **Process**: Backlink impact and performance monitoring
- **Dependencies**: Backlink data, performance metrics
- **Files**: `flows/sync-functions.php`, `modules/config/kpi-config.php`
- **Tracking**: Performance monitoring
- **Output**: Performance tracking data
---
## 5. CONTENT PERSONALIZATION WORKFLOW
### 5.1 Field Detection Process
#### Step 1: Content Analysis
- **Function**: `igny8_analyze_content_for_fields()`
- **Process**: AI-powered field detection from content
- **Dependencies**: OpenAI API, content data
- **Files**: `modules/modules-pages/personalize/content-generation.php`, `ai/integration.php`
- **Analysis**: Content field analysis
- **Output**: Detected personalization fields
#### Step 2: Field Configuration
- **Function**: `igny8_configure_fields()`
- **Process**: Field configuration and customization
- **Dependencies**: Field data, user preferences
- **Files**: `modules/modules-pages/personalize/content-generation.php`, `flows/sync-functions.php`
- **Configuration**: Field setup and customization
- **Output**: Configured personalization fields
#### Step 3: Field Validation
- **Function**: `igny8_validate_fields()`
- **Process**: Field validation and testing
- **Dependencies**: Field data, validation rules
- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php`
- **Validation**: Field validation and testing
- **Output**: Validated personalization fields
### 5.2 Content Rewriting Process
#### Step 1: Content Rewriting
- **Function**: `igny8_rewrite_content()`
- **Process**: AI-powered content personalization
- **Dependencies**: OpenAI API, user data
- **Files**: `ai/integration.php`, `ai/openai-api.php`
- **Rewriting**: Content personalization
- **Output**: Personalized content
#### Step 2: Content Optimization
- **Function**: `igny8_optimize_personalized_content()`
- **Process**: Personalized content optimization
- **Dependencies**: Personalized content, optimization rules
- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php`
- **Optimization**: Content optimization
- **Output**: Optimized personalized content
#### Step 3: Content Validation
- **Function**: `igny8_validate_personalized_content()`
- **Process**: Personalized content quality validation
- **Dependencies**: Personalized content, quality rules
- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php`
- **Validation**: Content quality validation
- **Output**: Validated personalized content
### 5.3 Frontend Integration
#### Step 1: Frontend Setup
- **Function**: `igny8_setup_frontend()`
- **Process**: Frontend personalization setup
- **Dependencies**: Frontend configuration, personalization data
- **Files**: `modules/modules-pages/personalize/front-end.php`, `flows/sync-functions.php`
- **Setup**: Frontend configuration
- **Output**: Configured frontend personalization
#### Step 2: User Interface
- **Function**: `igny8_display_personalization_interface()`
- **Process**: Personalization interface display
- **Dependencies**: Frontend templates, user data
- **Files**: `assets/js/core.js`, `modules/components/forms-tpl.php`
- **Display**: Personalization interface
- **Output**: User personalization interface
#### Step 3: Content Delivery
- **Function**: `igny8_deliver_personalized_content()`
- **Process**: Personalized content delivery
- **Dependencies**: Personalized content, delivery systems
- **Files**: `flows/sync-functions.php`, `core/db/db.php`
- **Delivery**: Content delivery
- **Output**: Delivered personalized content
---
## 6. AUTOMATION WORKFLOWS
### 6.1 Schedule Management
#### Step 1: Schedule Configuration
- **Function**: `igny8_configure_schedules()`
- **Process**: Automation schedule setup and configuration
- **Dependencies**: Schedule data, automation rules
- **Files**: `core/pages/settings/schedules.php`, `flows/sync-functions.php`
- **Configuration**: Schedule setup
- **Output**: Configured automation schedules
#### Step 2: Task Scheduling
- **Function**: `igny8_schedule_tasks()`
- **Process**: Automated task scheduling
- **Dependencies**: WordPress CRON, task data
- **Files**: `core/cron/igny8-cron-master-dispatcher.php`, `core/cron/igny8-cron-handlers.php`
- **Scheduling**: Task scheduling
- **Output**: Scheduled automation tasks
#### Step 3: Schedule Monitoring
- **Function**: `igny8_monitor_schedules()`
- **Process**: Schedule performance monitoring
- **Dependencies**: Schedule data, monitoring systems
- **Files**: `core/cron/igny8-cron-master-dispatcher.php`, `core/admin/global-helpers.php`
- **Monitoring**: Schedule monitoring
- **Output**: Schedule monitoring data
### 6.2 Automation Execution
#### Step 1: Task Execution
- **Function**: `igny8_execute_automation()`
- **Process**: Automated task execution
- **Dependencies**: Task data, execution systems
- **Files**: `core/cron/igny8-cron-handlers.php`, `flows/sync-functions.php`
- **Execution**: Task execution
- **Output**: Executed automation tasks
#### Step 2: Process Monitoring
- **Function**: `igny8_monitor_automation()`
- **Process**: Automation process monitoring
- **Dependencies**: Automation data, monitoring systems
- **Files**: `core/cron/igny8-cron-master-dispatcher.php`, `core/admin/global-helpers.php`
- **Monitoring**: Process monitoring
- **Output**: Automation monitoring data
#### Step 3: Error Handling
- **Function**: `igny8_handle_automation_errors()`
- **Process**: Automation error handling and recovery
- **Dependencies**: Error data, recovery systems
- **Files**: `core/cron/igny8-cron-handlers.php`, `flows/sync-functions.php`
- **Handling**: Error handling and recovery
- **Output**: Error handling results
### 6.3 Performance Optimization
#### Step 1: Performance Analysis
- **Function**: `igny8_analyze_automation_performance()`
- **Process**: Automation performance analysis
- **Dependencies**: Performance data, analysis algorithms
- **Files**: `core/admin/global-helpers.php`, `modules/config/kpi-config.php`
- **Analysis**: Performance analysis
- **Output**: Performance insights
#### Step 2: Optimization Adjustment
- **Function**: `igny8_optimize_automation()`
- **Process**: Automation optimization
- **Dependencies**: Performance data, optimization rules
- **Files**: `flows/sync-functions.php`, `ai/modules-ai.php`
- **Optimization**: Automation optimization
- **Output**: Optimized automation
#### Step 3: Continuous Improvement
- **Function**: `igny8_improve_automation()`
- **Process**: Continuous automation improvement
- **Dependencies**: Performance data, improvement algorithms
- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php`
- **Improvement**: Continuous improvement
- **Output**: Improved automation
---
## 7. ANALYTICS & REPORTING WORKFLOW
### 7.1 Data Collection
#### Step 1: Metrics Collection
- **Function**: `igny8_collect_metrics()`
- **Process**: Performance metrics collection
- **Dependencies**: Analytics APIs, content data
- **Files**: `core/admin/global-helpers.php`, `modules/config/kpi-config.php`
- **Collection**: Metrics gathering
- **Output**: Collected performance metrics
#### Step 2: Data Processing
- **Function**: `igny8_process_analytics_data()`
- **Process**: Analytics data processing and preparation
- **Dependencies**: Raw data, processing algorithms
- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php`
- **Processing**: Data processing
- **Output**: Processed analytics data
#### Step 3: Data Storage
- **Function**: `igny8_store_analytics_data()`
- **Process**: Analytics data storage and organization
- **Dependencies**: Processed data, database systems
- **Files**: `core/db/db.php`, `flows/sync-functions.php`
- **Storage**: Data storage
- **Output**: Stored analytics data
### 7.2 Analysis & Insights
#### Step 1: Data Analysis
- **Function**: `igny8_analyze_analytics_data()`
- **Process**: Analytics data analysis and insights
- **Dependencies**: Stored data, analysis algorithms
- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php`
- **Analysis**: Data analysis
- **Output**: Analytics insights
#### Step 2: Trend Analysis
- **Function**: `igny8_analyze_trends()`
- **Process**: Performance trend analysis
- **Dependencies**: Historical data, trend algorithms
- **Files**: `ai/modules-ai.php`, `core/admin/global-helpers.php`
- **Analysis**: Trend analysis
- **Output**: Trend insights
#### Step 3: Predictive Analytics
- **Function**: `igny8_predict_performance()`
- **Process**: Performance prediction and forecasting
- **Dependencies**: Historical data, prediction algorithms
- **Files**: `ai/modules-ai.php`, `flows/sync-functions.php`
- **Prediction**: Performance prediction
- **Output**: Predictive insights
### 7.3 Reporting & Visualization
#### Step 1: Report Generation
- **Function**: `igny8_generate_reports()`
- **Process**: Automated report generation
- **Dependencies**: Analytics data, report templates
- **Files**: `modules/components/kpi-tpl.php`, `flows/sync-functions.php`
- **Generation**: Report generation
- **Output**: Generated reports
#### Step 2: Data Visualization
- **Function**: `igny8_visualize_data()`
- **Process**: Analytics data visualization
- **Dependencies**: Analytics data, visualization tools
- **Files**: `modules/components/kpi-tpl.php`, `assets/js/core.js`
- **Visualization**: Data visualization
- **Output**: Visualized analytics data
#### Step 3: Report Distribution
- **Function**: `igny8_distribute_reports()`
- **Process**: Report distribution and delivery
- **Dependencies**: Generated reports, distribution systems
- **Files**: `flows/sync-functions.php`, `core/admin/global-helpers.php`
- **Distribution**: Report distribution
- **Output**: Distributed reports
---
## 8. INTEGRATION WORKFLOWS
### 8.1 WordPress Integration
#### Step 1: WordPress Setup
- **Function**: `igny8_setup_wordpress_integration()`
- **Process**: WordPress integration setup
- **Dependencies**: WordPress hooks, plugin system
- **Files**: `igny8.php`, `core/admin/init.php`
- **Setup**: WordPress integration
- **Output**: Integrated WordPress functionality
#### Step 2: Post Meta Management
- **Function**: `igny8_manage_post_meta()`
- **Process**: WordPress post metadata management
- **Dependencies**: WordPress post system, metadata
- **Files**: `core/db/db.php`, `flows/sync-functions.php`
- **Management**: Post metadata management
- **Output**: Managed post metadata
#### Step 3: Taxonomy Integration
- **Function**: `igny8_integrate_taxonomies()`
- **Process**: Custom taxonomy integration
- **Dependencies**: WordPress taxonomy system
- **Files**: `core/db/db.php`, `core/admin/init.php`
- **Integration**: Taxonomy integration
- **Output**: Integrated taxonomies
### 8.2 AI Service Integration
#### Step 1: AI Service Setup
- **Function**: `igny8_setup_ai_services()`
- **Process**: AI service integration setup
- **Dependencies**: AI APIs, authentication
- **Files**: `ai/integration.php`, `ai/openai-api.php`
- **Setup**: AI service setup
- **Output**: Configured AI services
#### Step 2: API Management
- **Function**: `igny8_manage_ai_apis()`
- **Process**: AI API management and optimization
- **Dependencies**: API credentials, rate limiting
- **Files**: `ai/openai-api.php`, `ai/runware-api.php`
- **Management**: API management
- **Output**: Managed AI APIs
#### Step 3: Performance Monitoring
- **Function**: `igny8_monitor_ai_performance()`
- **Process**: AI service performance monitoring
- **Dependencies**: AI services, monitoring systems
- **Files**: `ai/integration.php`, `core/admin/global-helpers.php`
- **Monitoring**: AI performance monitoring
- **Output**: AI performance data
### 8.3 Database Integration
#### Step 1: Database Setup
- **Function**: `igny8_setup_database()`
- **Process**: Database table creation and setup
- **Dependencies**: Database system, table schemas
- **Files**: `core/db/db.php`, `install.php`
- **Setup**: Database setup
- **Output**: Configured database
#### Step 2: Data Migration
- **Function**: `igny8_migrate_data()`
- **Process**: Data migration and version management
- **Dependencies**: Database system, migration scripts
- **Files**: `core/db/db-migration.php`, `flows/sync-functions.php`
- **Migration**: Data migration
- **Output**: Migrated data
#### Step 3: Performance Optimization
- **Function**: `igny8_optimize_database()`
- **Process**: Database performance optimization
- **Dependencies**: Database system, optimization rules
- **Files**: `core/db/db.php`, `flows/sync-functions.php`
- **Optimization**: Database optimization
- **Output**: Optimized database
---
## Technical Implementation Details
### Workflow Dependencies
- **WordPress Core**: Hooks, actions, filters
- **Database Layer**: Custom tables, queries, migrations
- **AI Services**: OpenAI, Runware APIs
- **Frontend**: JavaScript, CSS, responsive design
- **Automation**: WordPress CRON, scheduled tasks
### File Structure
- **Core Files**: Plugin initialization and setup
- **Module Files**: Feature-specific implementations
- **AI Integration**: AI service integrations
- **Workflows**: Process automation and management
- **Assets**: Frontend resources and templates
### Performance Considerations
- **Caching**: Data caching and optimization
- **Database**: Query optimization and indexing
- **AI APIs**: Rate limiting and cost optimization
- **Automation**: Efficient task scheduling and execution
- **Monitoring**: Performance tracking and optimization
This comprehensive workflows documentation covers all aspects of the Igny8 AI SEO Plugin's workflow processes, providing detailed step-by-step guidance for each workflow, including functions, dependencies, and file references.

View File

@@ -0,0 +1,122 @@
igny8-ai-seo/
├── ai/
│ ├── _README.php
│ ├── integration.php
│ ├── model-rates-config.php
│ ├── modules-ai.php
│ ├── openai-api.php
│ ├── prompts-library.php
│ ├── runware-api.php
│ └── writer/
│ ├── content/
│ └── images/
│ └── image-generation.php
├── assets/
│ ├── ai-images/
│ ├── css/
│ │ ├── core-backup.css
│ │ ├── core.css
│ │ └── image-injection.css
│ ├── img/
│ ├── js/
│ │ ├── core.js
│ │ └── image-queue-processor.js
│ ├── shortcodes/
│ │ ├── _README.php
│ │ └── image-gallery.php
│ └── templates/
│ ├── igny8_clusters_template.csv
│ ├── igny8_ideas_template.csv
│ └── igny8_keywords_template.csv
├── CHANGELOG_live.md
├── core/
│ ├── _README.php
│ ├── admin/
│ │ ├── ajax.php
│ │ ├── global-helpers.php
│ │ ├── init.php
│ │ ├── menu.php
│ │ ├── meta-boxes.php
│ │ └── module-manager-class.php
│ ├── cron/
│ │ ├── igny8-cron-handlers.php
│ │ └── igny8-cron-master-dispatcher.php
│ ├── db/
│ │ ├── db-migration.php
│ │ └── db.php
│ └── global-layout.php
├── debug/
│ ├── _README.php
│ ├── debug.php
│ ├── module-debug.php
│ └── monitor-helpers.php
├── docs/
│ ├── _README.php
│ ├── COMPLETE_FEATURES_LIST.md
│ ├── COMPLETE_FUNCTION_REFERENCE.md
│ ├── COMPLETE_IMAGE_GENERATION_AUDIT.md
│ ├── COMPLETE_WORKFLOWS_DOCUMENTATION.md
│ ├── FILE_TREE.txt
│ ├── IGNY8_PAGES_TABLE.md
│ ├── IGNY8_SNAPSHOT_V5.2.0.md
│ └── TROUBLESHOOTING_Converting_to_blocks_and_image_shortcode_injection.md
├── flows/
│ ├── sync-ajax.php
│ ├── sync-functions.php
│ └── sync-hooks.php
├── igny8-wp-load-handler.php
├── igny8.php
├── install.php
├── modules/
│ ├── _README.php
│ ├── analytics/
│ │ └── analytics.php
│ ├── components/
│ │ ├── _README.php
│ │ ├── actions-tpl.php
│ │ ├── export-modal-tpl.php
│ │ ├── filters-tpl.php
│ │ ├── forms-tpl.php
│ │ ├── import-modal-tpl.php
│ │ ├── kpi-tpl.php
│ │ ├── pagination-tpl.php
│ │ └── table-tpl.php
│ ├── config/
│ │ ├── _README.php
│ │ ├── filters-config.php
│ │ ├── forms-config.php
│ │ ├── import-export-config.php
│ │ ├── kpi-config.php
│ │ └── tables-config.php
│ ├── help/
│ │ ├── docs.php
│ │ ├── function-testing.php
│ │ ├── help.php
│ │ └── system-testing.php
│ ├── home.php
│ ├── planner/
│ │ ├── clusters.php
│ │ ├── ideas.php
│ │ ├── keywords.php
│ │ └── planner.php
│ ├── settings/
│ │ ├── general-settings.php
│ │ ├── import-export.php
│ │ ├── integration.php
│ │ ├── schedules.php
│ │ └── status.php
│ ├── thinker/
│ │ ├── image-testing.php
│ │ ├── profile.php
│ │ ├── prompts.php
│ │ ├── strategies.php
│ │ └── thinker.php
│ └── writer/
│ ├── drafts.php
│ ├── published.php
│ ├── tasks.php
│ └── writer.php
├── shortcodes/
│ ├── ai-shortcodes.php
│ └── writer-shortcodes.php
└── uninstall.php

View File

@@ -0,0 +1,162 @@
# Igny8 AI SEO Plugin - Complete Pages Table
## Overview
This table provides a comprehensive list of all pages in the Igny8 AI SEO Plugin, including their URLs, purposes, and functionality.
---
## Main Navigation Pages
| Page Name | URL | Purpose | Module | Subpages |
|-----------|-----|---------|--------|----------|
| **Dashboard** | `admin.php?page=igny8-home` | Main dashboard with complete AI workflow guide | Core | None |
| **Planner** | `admin.php?page=igny8-planner` | Content planning and keyword research | Planner | 4 subpages |
| **Writer** | `admin.php?page=igny8-writer` | Content creation and writing tools | Writer | 3 subpages |
| **Optimizer** | `admin.php?page=igny8-optimizer` | SEO optimization and performance tools | Optimizer | 2 subpages |
| **Linker** | `admin.php?page=igny8-linker` | Link building and backlink management | Linker | 2 subpages |
| **Personalize** | `admin.php?page=igny8-personalize` | Content personalization and targeting | Personalize | 4 subpages |
| **Thinker** | `admin.php?page=igny8-thinker` | AI thinker and strategy tools | Thinker | 4 subpages |
| **Analytics** | `admin.php?page=igny8-analytics` | Performance analytics and reporting | Analytics | None |
| **Schedules** | `admin.php?page=igny8-schedules` | Smart automation schedules | Schedules | None |
| **Settings** | `admin.php?page=igny8-settings` | Plugin configuration and settings | Core | 3 subpages |
| **Help** | `admin.php?page=igny8-help` | Documentation and support resources | Core | 3 subpages |
---
## Planner Module Pages
| Page Name | URL | Purpose | Description |
|-----------|-----|---------|-------------|
| **Planner Dashboard** | `admin.php?page=igny8-planner` | Main planner overview | Overview of keywords, clusters, and ideas |
| **Keywords** | `admin.php?page=igny8-planner&sm=keywords` | Keyword management | Manage keywords, track search volumes, organize by intent and difficulty |
| **Clusters** | `admin.php?page=igny8-planner&sm=clusters` | Keyword clustering | Group related keywords into content clusters for better topical authority |
| **Ideas** | `admin.php?page=igny8-planner&sm=ideas` | Content ideas generation | Generate and organize content ideas based on keyword research |
| **Mapping** | `admin.php?page=igny8-planner&sm=mapping` | Content mapping | Map keywords and clusters to existing pages and content |
---
## Writer Module Pages
| Page Name | URL | Purpose | Description |
|-----------|-----|---------|-------------|
| **Writer Dashboard** | `admin.php?page=igny8-writer` | Main writer overview | Overview of content tasks and workflow |
| **Tasks** | `admin.php?page=igny8-writer&sm=tasks` | Content queue management | Manage content tasks and work queue |
| **Drafts** | `admin.php?page=igny8-writer&sm=drafts` | Draft content management | Manage content drafts and work in progress |
| **Published** | `admin.php?page=igny8-writer&sm=published` | Published content | View and manage published content |
---
## Optimizer Module Pages
| Page Name | URL | Purpose | Description |
|-----------|-----|---------|-------------|
| **Optimizer Dashboard** | `admin.php?page=igny8-optimizer` | Main optimizer overview | Overview of optimization tools and performance |
| **Audits** | `admin.php?page=igny8-optimizer&sm=audits` | SEO audits | Run comprehensive SEO audits on content and pages |
| **Suggestions** | `admin.php?page=igny8-optimizer&sm=suggestions` | Optimization suggestions | Get AI-powered optimization suggestions for better rankings |
---
## Linker Module Pages
| Page Name | URL | Purpose | Description |
|-----------|-----|---------|-------------|
| **Linker Dashboard** | `admin.php?page=igny8-linker` | Main linker overview | Overview of link building tools and campaigns |
| **Backlinks** | `admin.php?page=igny8-linker&sm=backlinks` | Backlink management | Track and manage backlink profile and authority |
| **Campaigns** | `admin.php?page=igny8-linker&sm=campaigns` | Link building campaigns | Plan and execute link building campaigns effectively |
---
## Personalize Module Pages
| Page Name | URL | Purpose | Description |
|-----------|-----|---------|-------------|
| **Personalize Dashboard** | `admin.php?page=igny8-personalize` | Main personalization overview | Overview of personalization tools and settings |
| **Settings** | `admin.php?page=igny8-personalize&sm=settings` | Personalization settings | Configure global settings, display options, and advanced personalization settings |
| **Content Generation** | `admin.php?page=igny8-personalize&sm=content-generation` | AI content generation | Configure AI prompts, field detection, and content generation parameters |
| **Rewrites** | `admin.php?page=igny8-personalize&sm=rewrites` | Content variations | View and manage personalized content variations and rewrites |
| **Front-end** | `admin.php?page=igny8-personalize&sm=front-end` | Frontend implementation | Manage front-end display settings, shortcode usage, and implementation guides |
---
## Thinker Module Pages
| Page Name | URL | Purpose | Description |
|-----------|-----|---------|-------------|
| **Thinker Dashboard** | `admin.php?page=igny8-thinker&sp=main` | Main AI thinker overview | Overview of AI tools and strategies |
| **Prompts** | `admin.php?page=igny8-thinker&sp=prompts` | AI prompts management | Manage and configure AI prompts for content generation |
| **Profile** | `admin.php?page=igny8-thinker&sp=profile` | AI profile settings | Configure AI personality and writing style |
| **Strategies** | `admin.php?page=igny8-thinker&sp=strategies` | Content strategies | Plan and manage content strategies and approaches |
| **Image Testing** | `admin.php?page=igny8-thinker&sp=image-testing` | AI image testing | Test and configure AI image generation capabilities |
---
## Settings Pages
| Page Name | URL | Purpose | Description |
|-----------|-----|---------|-------------|
| **General Settings** | `admin.php?page=igny8-settings&sp=general` | Plugin configuration | Configure plugin settings, automation, and table preferences |
| **System Status** | `admin.php?page=igny8-settings&sp=status` | System monitoring | Monitor system health, database status, and module performance |
| **API Integration** | `admin.php?page=igny8-settings&sp=integration` | External integrations | Configure API keys and integrate with external services |
| **Import/Export** | `admin.php?page=igny8-settings&sp=import-export` | Data management | Import and export data, manage backups, and transfer content |
---
## Help Pages
| Page Name | URL | Purpose | Description |
|-----------|-----|---------|-------------|
| **Help & Support** | `admin.php?page=igny8-help&sp=help` | Main help page | Documentation and support resources for getting started |
| **Documentation** | `admin.php?page=igny8-help&sp=docs` | Complete documentation | Comprehensive documentation and guides |
| **System Testing** | `admin.php?page=igny8-help&sp=system-testing` | System diagnostics | Test system functionality and diagnose issues |
| **Function Testing** | `admin.php?page=igny8-help&sp=function-testing` | Function testing | Test individual functions and components |
---
## Special Pages
| Page Name | URL | Purpose | Description |
|-----------|-----|---------|-------------|
| **Analytics** | `admin.php?page=igny8-analytics` | Performance analytics | Performance analytics and reporting for data-driven decisions |
| **Schedules** | `admin.php?page=igny8-schedules` | Automation schedules | Content scheduling and automation for consistent publishing |
---
## Page Access Requirements
| Requirement | Description |
|-------------|-------------|
| **Capability** | All pages require `manage_options` capability |
| **Module Status** | Module pages only accessible if corresponding module is enabled |
| **User Context** | All pages require authenticated WordPress user |
---
## Page Structure Notes
### URL Parameters
- **`page`**: Main page identifier (e.g., `igny8-planner`)
- **`sm`**: Submodule parameter for module subpages (e.g., `keywords`, `clusters`)
- **`sp`**: Subpage parameter for settings/help pages (e.g., `general`, `docs`)
### Page Rendering
- All pages use `core/global-layout.php` as the master layout template
- Module pages use `modules/modules-pages/{module}.php` for content
- Settings/Help pages use `core/pages/{category}/{page}.php` for content
- All pages include breadcrumb navigation and submenu systems
### Dynamic Content
- Pages show different content based on module enablement status
- Subpages are conditionally rendered based on URL parameters
- All pages include workflow guides and progress tracking
---
## Summary
**Total Pages**: 25+ individual pages across 8 modules
**Main Modules**: Planner, Writer, Optimizer, Linker, Personalize, Thinker, Analytics, Schedules
**Core Pages**: Dashboard, Settings, Help
**Subpages**: 20+ subpages with specialized functionality
**Access Control**: All pages require admin privileges and module enablement
This comprehensive page structure provides a complete SEO management platform with specialized tools for each aspect of content creation, optimization, and performance tracking.

View File

@@ -0,0 +1,523 @@
# Igny8 AI SEO Plugin - Complete System Snapshot v0.1
## Summary Table
| System Component | Current State | Modules | Functions | Dependencies | Files Involved |
|------------------|---------------|---------|-----------|--------------|----------------|
| **Core System** | Fully Operational | 8 Active Modules | 200+ Functions | WordPress, Database | `igny8.php`, `core/`, `install.php`, `uninstall.php` |
| **AI Integration** | Advanced AI Processing | OpenAI, Runware | 25+ AI Functions | OpenAI API, Runware API | `ai/integration.php`, `ai/openai-api.php`, `ai/runware-api.php` |
| **Database Layer** | 15 Custom Tables | Data Management | 30+ DB Functions | MySQL, WordPress | `core/db/db.php`, `core/db/db-migration.php` |
| **Workflow Automation** | ⚠️ CRITICAL ISSUES IDENTIFIED | 7 Workflow Types | 40+ Automation Functions | WordPress CRON, Database | `flows/`, `core/cron/` |
| **Admin Interface** | Complete UI System | 8 Module Interfaces | 35+ UI Functions | WordPress Admin, JavaScript | `core/admin/`, `modules/` |
| **Frontend Integration** | Responsive Design | Personalization, Shortcodes | 15+ Frontend Functions | JavaScript, CSS | `assets/`, `modules/modules-pages/personalize/` |
| **Analytics & Reporting** | Advanced Analytics | KPI Tracking, Reporting | 25+ Analytics Functions | Database, WordPress | `core/admin/global-helpers.php`, `modules/config/` |
---
## ⚠️ CRITICAL SYSTEM ISSUES IDENTIFIED
### Cron vs Manual Function Discrepancies
- **HIGH RISK**: Cron functions have significant architectural differences from manual counterparts
- **Function Dependencies**: Cron handlers include extensive fallback logic suggesting unreliable function loading
- **User Context**: Cron handlers manually set admin user context while manual handlers rely on authenticated users
- **Error Handling**: Cron handlers suppress PHP warnings that manual handlers don't, potentially masking critical issues
- **Database Access**: Inconsistent database connection handling between cron and manual functions
### Affected Automation Functions
1. **Auto Cluster**: `igny8_auto_cluster_cron_handler()` vs `igny8_ajax_ai_cluster_keywords()`
2. **Auto Ideas**: `igny8_auto_generate_ideas_cron_handler()` vs `igny8_ajax_ai_generate_ideas()`
3. **Auto Queue**: `igny8_auto_queue_cron_handler()` vs `igny8_ajax_queue_ideas_to_writer()`
4. **Auto Content**: `igny8_auto_generate_content_cron_handler()` vs `igny8_ajax_ai_generate_content()`
5. **Auto Image**: `igny8_auto_generate_images_cron_handler()` vs `igny8_ajax_ai_generate_images_drafts()`
6. **Auto Publish**: `igny8_auto_publish_drafts_cron_handler()` vs `igny8_ajax_bulk_publish_drafts()`
### Impact Assessment
- **Manual Functions**: ✅ Healthy and functioning correctly
- **Cron Functions**: ❌ High risk of failure due to architectural differences
- **Recommendation**: 🔴 IMMEDIATE review and alignment required
- **Priority**: CRITICAL - Automation system reliability compromised
---
## 1. SYSTEM ARCHITECTURE OVERVIEW
### 1.1 Core System Components
- **Plugin Initialization**: Complete WordPress integration with hooks, actions, and filters
- **Database Management**: 15 custom tables with full migration and version control
- **Module System**: 8 active modules with dynamic loading and configuration
- **AI Integration**: Advanced OpenAI and Runware API integration
- **Automation System**: Comprehensive CRON-based workflow automation
- **Admin Interface**: Complete WordPress admin integration with responsive design
- **Frontend Integration**: Personalization and shortcode system
- **Analytics System**: Advanced KPI tracking and reporting
### 1.2 Current Version Status
- **Version**: 0.1
- **WordPress Compatibility**: 5.0+
- **PHP Requirements**: 7.4+
- **Database**: MySQL 5.7+
- **Status**: ⚠️ Production Ready with Critical Automation Issues
- **Last Updated**: January 15, 2025
- **Stability**: Stable (Manual Functions) / Unstable (Cron Functions)
- **Performance**: Optimized
- **Critical Issues**: Cron vs Manual function discrepancies identified
---
## 2. MODULE SYSTEM STATUS
### 2.1 Active Modules
#### **Planner Module** - Content Planning & Strategy
- **Status**: Fully Operational
- **Features**: Keyword research, AI clustering, content idea generation
- **Functions**: 25+ planning functions
- **Dependencies**: OpenAI API, Database, WordPress
- **Files**: `modules/modules-pages/planner.php`, `ai/modules-ai.php`
- **Workflow**: Keyword Research → Clustering → Ideas → Queue
#### **Writer Module** - Content Creation & Management
- **Status**: Fully Operational
- **Features**: AI content generation, draft management, publishing workflow
- **Functions**: 30+ writing functions
- **Dependencies**: AI APIs, WordPress, Database
- **Files**: `modules/modules-pages/writer.php`, `ai/modules-ai.php`
- **Workflow**: Task Creation → AI Generation → Draft Review → Publishing
#### **Optimizer Module** - SEO Analysis & Optimization
- **Status**: Fully Operational
- **Features**: Content audits, optimization suggestions, performance monitoring
- **Functions**: 20+ optimization functions
- **Dependencies**: SEO APIs, Database
- **Files**: `modules/modules-pages/optimizer.php`, `ai/modules-ai.php`
- **Workflow**: Content Analysis → Suggestions → Implementation → Monitoring
#### **Linker Module** - Backlink Management & Campaigns
- **Status**: Fully Operational
- **Features**: Backlink tracking, campaign management, authority building
- **Functions**: 25+ linking functions
- **Dependencies**: External APIs, Database
- **Files**: `modules/modules-pages/linker.php`, `flows/sync-functions.php`
- **Workflow**: Campaign Planning → Outreach → Tracking → Analysis
#### **Personalize Module** - AI Content Personalization
- **Status**: Fully Operational
- **Features**: AI personalization, user targeting, frontend integration
- **Functions**: 20+ personalization functions
- **Dependencies**: OpenAI API, Frontend
- **Files**: `modules/modules-pages/personalize/`, `ai/integration.php`
- **Workflow**: Field Detection → Content Rewriting → Frontend Display → Analytics
#### **Thinker Module** - AI Strategy & Prompt Management
- **Status**: Fully Operational
- **Features**: AI strategy, prompt management, content planning
- **Functions**: 15+ thinking functions
- **Dependencies**: AI APIs, Database
- **Files**: `core/pages/thinker/`, `ai/prompts-library.php`
- **Workflow**: Strategy Development → Prompt Management → Content Planning
#### **Analytics Module** - Performance Tracking & Reporting
- **Status**: Fully Operational
- **Features**: KPI tracking, performance monitoring, report generation
- **Functions**: 25+ analytics functions
- **Dependencies**: Database, WordPress
- **Files**: `core/admin/`, `modules/config/kpi-config.php`
- **Workflow**: Data Collection → Analysis → Visualization → Reporting
#### **Schedules Module** - Automation Management
- **Status**: Fully Operational
- **Features**: CRON management, workflow automation, task scheduling
- **Functions**: 15+ automation functions
- **Dependencies**: WordPress CRON, Database
- **Files**: `core/cron/`, `core/pages/settings/schedules.php`
- **Workflow**: Schedule Setup → Task Execution → Monitoring → Optimization
### 2.2 Module Dependencies
- **Core Dependencies**: WordPress, Database, PHP
- **AI Dependencies**: OpenAI API, Runware API
- **External Dependencies**: cURL, JSON, CSV
- **Internal Dependencies**: Module Manager, CRON System, Admin Interface
---
## 3. DATABASE ARCHITECTURE
### 3.1 Custom Tables (15 Tables)
#### **Core Data Tables**
- `igny8_keywords` - Keyword research and analysis data
- `igny8_tasks` - Content creation and writing tasks
- `igny8_data` - General plugin data and configurations
- `igny8_variations` - Content variations and personalization data
- `igny8_rankings` - SEO ranking and performance data
- `igny8_suggestions` - Optimization suggestions and recommendations
- `igny8_campaigns` - Link building and marketing campaigns
- `igny8_content_ideas` - AI-generated content ideas and concepts
- `igny8_clusters` - Keyword clusters and semantic groupings
- `igny8_sites` - Target sites and domain information
- `igny8_backlinks` - Backlink tracking and analysis data
- `igny8_mapping` - Data relationships and mappings
- `igny8_prompts` - AI prompts and templates
- `igny8_logs` - System logs and debugging information
- `igny8_ai_queue` - AI processing queue and task management
### 3.2 Database Features
- **Migration System**: Complete version control and data migration
- **Data Validation**: Comprehensive data integrity and validation
- **Performance Optimization**: Indexed queries and optimized operations
- **Backup & Recovery**: Automated backup and disaster recovery
- **Data Relationships**: Foreign key constraints and data integrity
### 3.3 WordPress Integration
- **Post Meta**: Advanced post metadata management
- **Taxonomies**: Custom taxonomies (sectors, clusters)
- **User Management**: Role-based access control
- **Options API**: Plugin settings and configuration
- **Transients**: Caching and performance optimization
---
## 4. AI INTEGRATION STATUS
### 4.1 OpenAI Integration
- **API Client**: Complete OpenAI API integration
- **Models Supported**: GPT-4, GPT-3.5-turbo, GPT-4-turbo
- **Features**: Content generation, keyword clustering, idea generation
- **Functions**: 15+ OpenAI functions
- **Cost Tracking**: API usage monitoring and cost optimization
- **Error Handling**: Robust error handling and recovery
### 4.2 Runware Integration
- **API Client**: Complete Runware API integration
- **Image Generation**: AI-powered image creation
- **Features**: Multi-size image generation, responsive images
- **Functions**: 10+ Runware functions
- **Image Processing**: Automated image processing and optimization
- **Integration**: WordPress media library integration
### 4.3 AI Workflow Integration
- **Content Generation**: Automated AI content creation
- **Image Generation**: Automated AI image creation
- **Personalization**: AI-powered content personalization
- **Optimization**: AI-driven content optimization
- **Analytics**: AI-powered performance analysis
---
## 5. WORKFLOW AUTOMATION STATUS ⚠️ CRITICAL ISSUES
### 5.1 CRON System
- **Master Dispatcher**: Centralized CRON job management
- **Job Handlers**: 10+ specialized CRON handlers
- **Scheduling**: Flexible task scheduling and management
- **Monitoring**: Health monitoring and performance tracking
- **Error Handling**: ⚠️ INCONSISTENT error handling between cron and manual functions
- **Status**: 🔴 HIGH RISK - Cron functions may fail due to architectural differences
### 5.2 Automation Workflows
- **Content Planning**: Automated keyword research and clustering
- **Content Creation**: Automated content generation and publishing
- **SEO Optimization**: Automated content optimization and monitoring
- **Link Building**: Automated outreach and backlink tracking
- **Personalization**: Automated content personalization
- **Analytics**: Automated reporting and performance tracking
### 5.3 Process Automation
- **Task Management**: Automated task creation and assignment
- **Content Processing**: Automated content generation and optimization
- **Publishing**: Automated content publishing and distribution
- **Monitoring**: Automated performance monitoring and alerting
- **Maintenance**: Automated system maintenance and optimization
---
## 6. ADMIN INTERFACE STATUS
### 6.1 User Interface Components
- **Dashboard**: Comprehensive dashboard with KPI tracking
- **Data Tables**: Advanced data tables with sorting and filtering
- **Forms**: Dynamic forms with validation and AJAX
- **Modals**: Import/export modals with progress tracking
- **Navigation**: Intuitive navigation with breadcrumbs
- **Responsive Design**: Mobile-optimized responsive design
### 6.2 Module Interfaces
- **Planner Interface**: Keyword research and clustering interface
- **Writer Interface**: Content creation and management interface
- **Optimizer Interface**: SEO analysis and optimization interface
- **Linker Interface**: Backlink management and campaign interface
- **Personalize Interface**: Content personalization interface
- **Thinker Interface**: AI strategy and prompt management interface
- **Analytics Interface**: Performance tracking and reporting interface
- **Schedules Interface**: Automation management interface
### 6.3 Component System
- **Reusable Components**: Table, form, filter, pagination components
- **Configuration System**: Dynamic configuration management
- **Template System**: Flexible template rendering system
- **Asset Management**: Optimized asset loading and caching
---
## 7. FRONTEND INTEGRATION STATUS
### 7.1 Personalization System
- **Shortcode Integration**: `[igny8]` shortcode for content personalization
- **Display Modes**: Button, inline, and automatic personalization modes
- **User Interface**: Customizable personalization forms and interfaces
- **Responsive Design**: Mobile-optimized personalization experience
- **Performance**: Fast-loading personalization features
### 7.2 Frontend Assets
- **JavaScript**: Core functionality and AJAX handling
- **CSS**: Responsive design and styling
- **Templates**: Frontend template system
- **Media**: Image and media handling
- **Performance**: Optimized asset delivery
### 7.3 User Experience
- **Personalization**: AI-powered content personalization
- **Responsive Design**: Mobile-first responsive design
- **Performance**: Optimized loading and performance
- **Accessibility**: WCAG compliant accessibility features
- **Integration**: Seamless WordPress theme integration
---
## 8. ANALYTICS & REPORTING STATUS
### 8.1 KPI Tracking
- **Performance Metrics**: Comprehensive performance tracking
- **Dashboard Analytics**: Real-time dashboard analytics
- **Trend Analysis**: Performance trend identification
- **Comparative Analysis**: Period-over-period comparison
- **Predictive Analytics**: AI-powered performance prediction
### 8.2 Reporting System
- **Automated Reports**: Scheduled performance reports
- **Custom Reports**: User-defined report creation
- **Export Functionality**: Multiple export formats
- **Report Scheduling**: Automated report delivery
- **Report Analytics**: Report usage and effectiveness tracking
### 8.3 Data Visualization
- **Interactive Charts**: Dynamic performance charts
- **Dashboard Customization**: Personalized dashboard configuration
- **Real-time Updates**: Live performance data updates
- **Visual Analytics**: Advanced data visualization
- **Performance Insights**: AI-powered performance insights
---
## 9. SECURITY & PERFORMANCE STATUS
### 9.1 Security Features
- **Data Sanitization**: Comprehensive input sanitization
- **Nonce Verification**: WordPress nonce security
- **User Permissions**: Role-based access control
- **API Security**: Secure API communication
- **Data Encryption**: Sensitive data encryption
### 9.2 Performance Optimization
- **Database Optimization**: Optimized queries and indexing
- **Caching System**: Advanced caching and performance optimization
- **Asset Optimization**: Minified and optimized assets
- **API Optimization**: Efficient API usage and rate limiting
- **Memory Management**: Optimized memory usage and garbage collection
### 9.3 Error Handling
- **Robust Error Handling**: Comprehensive error handling and recovery
- **Logging System**: Advanced logging and debugging
- **Monitoring**: System health monitoring and alerting
- **Recovery**: Automated error recovery and system restoration
- **Debugging**: Advanced debugging and troubleshooting tools
---
## 10. INTEGRATION STATUS
### 10.1 WordPress Integration
- **Core Integration**: Complete WordPress core integration
- **Hook System**: WordPress hooks, actions, and filters
- **Post System**: Advanced post metadata management
- **User System**: User role and permission management
- **Theme Integration**: Seamless theme integration
### 10.2 External Integrations
- **OpenAI API**: Advanced AI content generation
- **Runware API**: AI-powered image generation
- **SEO APIs**: External SEO service integration
- **Analytics APIs**: Performance tracking integration
- **Social APIs**: Social media integration
### 10.3 Data Integration
- **CSV Import/Export**: Comprehensive data portability
- **API Integration**: RESTful API integration
- **Webhook Support**: Real-time data synchronization
- **Data Synchronization**: Multi-platform data consistency
- **Custom Integrations**: Flexible integration development
---
## 11. CURRENT CAPABILITIES
### 11.1 Content Management
- **AI Content Generation**: Automated content creation using OpenAI
- **Image Generation**: AI-powered image creation using Runware
- **Content Optimization**: SEO-optimized content generation
- **Content Personalization**: AI-powered content personalization
- **Content Publishing**: Automated content publishing and distribution
### 11.2 SEO Optimization
- **Keyword Research**: Advanced keyword research and analysis
- **Content Audits**: Comprehensive SEO content audits
- **Optimization Suggestions**: AI-powered optimization recommendations
- **Performance Monitoring**: Real-time SEO performance tracking
- **Ranking Tracking**: Keyword ranking monitoring and analysis
### 11.3 Link Building
- **Backlink Tracking**: Automated backlink detection and analysis
- **Campaign Management**: Strategic link building campaign management
- **Outreach Automation**: Automated outreach and follow-up systems
- **Authority Building**: Long-term domain authority building
- **Relationship Management**: Influencer and industry relationship management
### 11.4 Analytics & Reporting
- **Performance Tracking**: Comprehensive performance metrics tracking
- **KPI Monitoring**: Key performance indicator monitoring
- **Trend Analysis**: Performance trend identification and analysis
- **Predictive Analytics**: AI-powered performance prediction
- **Custom Reporting**: User-defined report creation and scheduling
---
## 12. TECHNICAL SPECIFICATIONS
### 12.1 System Requirements
- **WordPress**: 5.0+ (Core platform)
- **PHP**: 7.4+ (Server-side language)
- **MySQL**: 5.7+ (Database system)
- **JavaScript**: ES6+ (Client-side functionality)
- **cURL**: HTTP client for API communication
- **JSON**: Data format for AI communication
### 12.2 Performance Specifications
- **Database**: 15 custom tables with optimized queries
- **Memory Usage**: Optimized memory usage and garbage collection
- **API Limits**: Efficient API usage and rate limiting
- **Caching**: Advanced caching and performance optimization
- **Asset Delivery**: Optimized asset loading and delivery
### 12.3 Security Specifications
- **Data Sanitization**: Comprehensive input sanitization
- **Nonce Verification**: WordPress nonce security
- **User Permissions**: Role-based access control
- **API Security**: Secure API communication
- **Data Encryption**: Sensitive data encryption
---
## 13. FUTURE ROADMAP
### 13.1 Planned Features
- **Advanced AI Models**: Integration with additional AI models
- **Enhanced Analytics**: Advanced analytics and reporting features
- **Mobile App**: Mobile application for content management
- **API Expansion**: Extended API capabilities
- **Third-party Integrations**: Additional third-party service integrations
### 13.2 Performance Improvements
- **Database Optimization**: Further database performance optimization
- **Caching Enhancement**: Advanced caching and performance optimization
- **API Optimization**: Further API usage optimization
- **Asset Optimization**: Enhanced asset optimization
- **Memory Optimization**: Advanced memory usage optimization
### 13.3 Security Enhancements
- **Advanced Security**: Enhanced security features
- **Data Protection**: Advanced data protection and privacy
- **Compliance**: Industry standard compliance and security
- **Audit Logging**: Enhanced audit logging and monitoring
- **Access Control**: Advanced access control and permissions
---
## 14. SUPPORT & MAINTENANCE
### 14.1 Support System
- **Documentation**: Comprehensive documentation and guides
- **Help System**: Integrated help system and tutorials
- **Community**: Community support and forums
- **Professional Support**: Enterprise-level support and maintenance
- **Training**: User training and onboarding
### 14.2 Maintenance
- **Regular Updates**: Regular plugin updates and improvements
- **Security Updates**: Security updates and patches
- **Performance Optimization**: Continuous performance optimization
- **Bug Fixes**: Bug fixes and issue resolution
- **Feature Enhancements**: New feature development and enhancement
### 14.3 Monitoring
- **System Health**: Continuous system health monitoring
- **Performance Tracking**: Performance monitoring and optimization
- **Error Tracking**: Error tracking and resolution
- **Usage Analytics**: Usage analytics and optimization
- **User Feedback**: User feedback collection and implementation
---
## 15. CONCLUSION
The Igny8 AI SEO Plugin v0.1 represents a comprehensive, AI-powered content management and SEO optimization platform with **COMPLETE REFACTOR IMPLEMENTED**. With 8 active modules, 200+ functions, and advanced AI integration, the system provides:
- **Complete Content Management**: From planning to publishing ✅
- **Advanced AI Integration**: OpenAI and Runware API integration ✅
- **Comprehensive SEO Tools**: Keyword research to performance monitoring ✅
- **Automated Workflows**: ⚠️ END-TO-END PROCESS AUTOMATION AT RISK
- **Advanced Analytics**: Performance tracking and reporting ✅
- **Scalable Architecture**: Modular, extensible design ✅
- **Production Ready**: ⚠️ MANUAL FUNCTIONS STABLE, CRON FUNCTIONS UNSTABLE
### Critical Status Summary
- **Manual Functions**: ✅ Fully operational and healthy
- **Cron Functions**: ❌ High risk of failure due to architectural discrepancies
- **Recommendation**: 🔴 IMMEDIATE action required to align cron functions with manual counterparts
- **Priority**: CRITICAL - Automation system reliability compromised
The system requires immediate attention to resolve cron vs manual function discrepancies before automation can be considered reliable for production use.
---
## File Structure Summary
### Core Files
- `igny8.php` - Main plugin file
- `install.php` - Installation script
- `uninstall.php` - Uninstallation script
- `igny8-wp-load-handler.php` - WordPress load handler
### Module Files
- `modules/modules-pages/` - Module interfaces
- `modules/components/` - Reusable components
- `modules/config/` - Configuration files
### AI Integration
- `ai/integration.php` - AI service integration
- `ai/openai-api.php` - OpenAI API client
- `ai/runware-api.php` - Runware API client
- `ai/modules-ai.php` - AI module functions
### Core System
- `core/admin/` - Admin interface
- `core/db/` - Database management
- `core/cron/` - Automation system
- `core/pages/` - Page templates
### Workflows
- `flows/` - Workflow automation
- `assets/` - Frontend assets
- `docs/` - Documentation
This snapshot provides a complete overview of the Igny8 AI SEO Plugin's current state, capabilities, and technical specifications, including critical automation issues that require immediate attention.

View File

@@ -0,0 +1,524 @@
IGNY8 WP PLUGIN TO IGNY8 APP MIGRATION PLAN
============================================
SECTION 1 - CODEBASE & DIRECTORY STRUCTURE (IGNY8 APP)
Goal: Design the top-level and module-level folder hierarchy for the new Django + React (Vite + Tailwind) full-stack IGNY8 App, preserving 1:1 parity with the current WordPress plugin modules while introducing clear API, automation, and AI boundaries.
1.1 Top-Level Repository Layout
igny8-app/
backend/ # Django backend (core API, models, automation)
igny8_core/ # Main Django app (replaces plugin core)
__init__.py
settings.py # Env-aware settings (local / staging / prod)
urls.py
wsgi.py
asgi.py
modules/ # 1:1 migration of plugin modules
planner/
models.py
views.py
serializers.py
tasks.py # CRON/async/queue handlers
urls.py
writer/
thinker/
... (future: linker, optimizer, etc.)
api/ # Unified REST entrypoints
routes/
ai/
content.py
images.py
publish.py
reparse.py
system/
middleware/
auth/ # Multi-tenant + RBAC + Stripe linkage
models.py
roles.py
views.py
signals.py
serializers.py
utils/ # Shared helpers (AI, logs, etc.)
cron/ # Timed jobs (mapped from plugin CRON)
migrations/
manage.py
frontend/ # React + Vite + Tailwind UI
src/
app/
globals.css
layout.tsx
pages/ # Page-per-module parity with WP admin
PlannerPage.tsx
WriterPage.tsx
ThinkerPage.tsx
DashboardPage.tsx
components/
ui/ # Local UI library (Card, Button, etc.)
layout/
charts/
hooks/
services/ # API clients (axios/fetch for each module)
store/ # Zustand/Redux state
vite.config.js
package.json
tsconfig.json
infra/ # Docker, Portainer, Caddy, Postgres, Redis
docker-compose.yml
caddy/
scripts/
dev-config.yml
README.md
docs/
MIGRATION_PLAN.md
API_REFERENCE.md
DATABASE_MAP.md
.env / .env.dev / .env.prod # environment-specific configs
1.2 Key Design Rules
- Modules: Each former WordPress module = isolated Django app inside /backend/modules/
- API Layer: Use /backend/api/routes/{domain}/ for all external triggers
- Frontend: Page = Module parity (PlannerPage.tsx, etc.)
- UI Library: /frontend/src/components/ui/ replaces @/components/ui/ from TailAdmin
- DevOps: Single infra/docker-compose.yml defines the full stack
- Roles/Billing: /backend/auth/roles.py + Stripe integration
- Docs: /docs folder in-repo
1.3 Role-Based Access (RBAC) Foundation
Roles:
- Owner: Full access to all tenants, billing, and automation
- Admin: Manage content modules, view billing but not edit
- Editor: Generate AI content, manage clusters/tasks
- Viewer: Read-only dashboards
- SystemBot: Automation + CRON actions (No UI, API-only)
1.4 AI-Assisted Dev Environment (Cursor / Local PC)
- Expose selective sub-repos /backend/modules/, /frontend/src/, /docs/ to GitHub/GitLab
- Keep /infra & .env excluded (.gitignore) for security
- AI Context Indexing: Each module includes README.md describing schema, routes, and functions
- Dockerized Dev Setup: Cursor runs Node + Python containers via docker-compose
- Source Sync Hooks: Git hooks ensure migrations and schema docs stay synchronized
SECTION 2 - DATABASE & SCHEMA MIGRATION PLAN
Goal: Migrate all IGNY8 custom database tables and essential data from WordPress to a Django + PostgreSQL schema.
2.1 IGNY8 Core Tables to Migrate 1:1
WP Table → Django Model:
- igny8_keywords → Keywords
- igny8_clusters → Clusters
- igny8_content_ideas → ContentIdeas
- igny8_tasks → Tasks
- igny8_variations → Variations
- igny8_prompts → Prompts
- igny8_logs → Logs
- igny8_images → Images
- igny8_schedules → Schedules
- igny8_settings → Settings (Split into SystemSettings and UserSettings)
- igny8_accounts → Accounts
- igny8_links → Links (Future)
- igny8_metrics → Metrics
2.2 WordPress Default Tables (Partial Extraction)
- wp_posts → WPPosts (ID, post_title, post_content, post_status, post_type, post_date)
- wp_postmeta → WPPostMeta (post_id, meta_key, meta_value - only IGNY8 keys)
- wp_users → WPUserMap (ID, user_email, display_name)
- wp_options → WPOptions (option_name, option_value - plugin config only)
2.3 Cross-System Integration Map
- Keywords/Clusters: App → WP via WP REST API
- Tasks/Content: App → WP via WP /wp-json/igny8/v1/import-content
- Images: App → WP via WP Media REST endpoint
- Settings: Bidirectional sync
- Logs/Metrics: App-only
2.4 Schema Example: Tasks Model (Django ORM)
Includes: id, cluster, idea, task_type, ai_model, prompt, raw_ai_response, status, result, timestamps, user, tenant
2.5 Database Migration & Sync Process
Steps: Export → Create Django models → Import data → Verify → Migrate WP data → Run integrity tests → Enable periodic sync
2.6 Tenant-Aware Model Design
Every model inherits from TenantBaseModel with tenant, created_at, updated_at fields.
SECTION 3 - COMPONENT & UI MAPPING PLAN
Goal: Rebuild the WordPress plugin's admin interface as React/Vite pages.
3.1 One-to-One Module → Page Mapping
- Planner Dashboard → PlannerPage.tsx (/planner)
- Writer Dashboard → WriterPage.tsx (/writer)
- Thinker → ThinkerPage.tsx (/thinker)
- Optimizer (Phase 2) → OptimizerPage.tsx (/optimizer)
- Linker (Phase 2) → LinkerPage.tsx (/linker)
- Settings → SettingsPage.tsx (/settings)
- Dashboard → DashboardPage.tsx (/dashboard)
3.2 Shared Frontend Component Map
- DataTable, FilterPanel, FormModal, ActionButtons, StatsCards, ToastNotifications, AIStatusBar, ConfirmDialog, TenantSwitcher
3.3 UI Data Flow Diagram (Per Module)
User → React Page → Zustand Store → API Service → Django API → AI Processing → Database → React Refresh
3.4 State Management Rules (Zustand)
Each module defines its store following the same interface pattern.
SECTION 4 - API ROUTES & AUTOMATION TRIGGERS
Goal: Define all Django REST API endpoints and automation triggers.
4.1 Unified API Structure
- /api/planner/ - Keyword → Cluster → Idea generation
- /api/writer/ - Content generation, reparsing, image injection
- /api/thinker/ - Prompt testing, image model handling
- /api/settings/ - Model rates, limits, keys, Stripe data
- /api/system/ - Metrics, logs, and CRON control
- /api/auth/ - Tenant registration, login, roles
- /api/publish/ - Push content/images to WP site
4.2 Example Endpoint Tree
Detailed endpoint listing for each module with GET/POST/DELETE methods
4.3 API → AI Execution Chain
Common AI service layer (/backend/utils/ai.py) handles all AI requests
4.4 Automation Triggers (Replacing WordPress CRON)
- auto_cluster_keywords: every 6h
- auto_generate_ideas: daily
- auto_generate_content: hourly
- auto_generate_images: hourly
- auto_publish_posts: daily
- cleanup_logs: weekly
4.5 Queue Control Logic
Shared queue core (/backend/utils/queue_manager.py) for automation and manual triggers
4.6 Stripe Integration (API)
Endpoints for billing, subscription, webhooks
4.7 AI Key & Model Management
Configured through /api/settings/ai-models
4.8 Example: /api/writer/generate/<id> Workflow
8-step process from React frontend to WordPress publish
4.9 Authentication & Role Guard Middleware
All endpoints protected by RoleRequired decorator
4.10 CRON & API Coherence
CRON functions reuse the same endpoints as manual buttons
SECTION 5 - WORDPRESS INTEGRATION & SYNC LAYER
Goal: Design a clean, secure bridge between Django backend and WordPress sites.
5.1 Integration Overview
- AI Content Publish: App → WP
- Image Upload: App → WP
- Settings Sync: Bidirectional
- Post Status Check: WP → App
- Keyword/Cluster Sync: App → WP
- Auth Link: Manual JWT-based connection
5.2 Connection Setup (One-Time Auth Handshake)
WPIntegration model stores site_url, api_key, tenant, status, last_sync
5.3 WordPress REST Endpoints (Added to Plugin)
- /wp-json/igny8/v1/import-content
- /wp-json/igny8/v1/upload-image
- /wp-json/igny8/v1/sync-settings
- /wp-json/igny8/v1/status
- /wp-json/igny8/v1/pull-updates
5.4 Django → WordPress Communication Flow
Example publishing workflow with request/response structure
5.5 WordPress Plugin: Receiving Side Implementation
Minimalistic handler example code
5.6 Bidirectional Settings Sync
Field mapping between App and WP
5.7 Multi-Tenant Integration Mapping
Each tenant can connect multiple WP sites
5.8 Sync Safety & Rate Control Layer
Queue throttling, error handling, fallback mode, audit logs
5.9 Security Model
JWT with 12h expiry, HMAC signatures, CORS whitelist
5.10 Example: Full Publish & Feedback Cycle
Complete workflow from user action to periodic sync
5.11 Future Extension (Phase-2+)
Planned integrations for metrics, links, user data, schema
SECTION 6 - DEPLOYMENT, ENVIRONMENT & LOCAL DEV PLAN
Goal: Define complete development, deployment, and synchronization environment.
6.1 Full Stack Architecture Summary
- Reverse Proxy: Caddy (80/443)
- Frontend: React (Vite) + Node 20 (8020→8021)
- Backend: Django + Gunicorn (8010→8011)
- Database: PostgreSQL 15 (5432)
- Cache/Queue: Redis 7 (6379)
- Admin DB UI: pgAdmin4 (5050)
- File Manager: Filebrowser (8080)
- Docker Manager: Portainer CE (9443/8000)
6.2 Environment File (.env)
Variables for DB, Redis, AI keys, Stripe, domains
6.3 Docker Compose Structure
Service definitions with volumes, networks, environment variables
6.4 Local Development Setup (Cursor AI)
Steps for local development with live mounts
6.5 Portainer Stack Deployment
Production deployment via Portainer stacks
6.6 Environment-Specific Configs
Separate configs for local, staging, production
6.7 Backup & Recovery Procedures
Automated backups for database, media, configs
SECTION 7 - DATA MIGRATION & SYNC STRATEGY
Goal: Extract, transform, and import all IGNY8 plugin data from WordPress MySQL to PostgreSQL.
7.1 Migration Overview
Extract from WP → Transform schema → Import to Django → Validate → Sync
7.2 Table Mapping Details
Complete mapping of all WP tables to Django models
7.3 Migration Phases
1. Extraction (Dump plugin tables)
2. Transformation (Convert to Postgres schema)
3. Import (Bulk insert via Django)
4. Validation (Compare counts, hashes)
5. Sync (Enable real-time sync to WP)
7.4 Extraction Script Example
Python script using mysql.connector
7.5 Transformation Example
Data transformation script
7.6 Import to Django
Django management command for bulk import
7.7 Verification Step
Comparison script for validation
7.8 Syncable Tables (Remain Linked to WP)
Tables that maintain bidirectional sync
7.9 Migration Validation Dashboard
UI section showing migration status
7.10 Rollback Strategy
Procedure for handling migration failures
7.11 Final Verification Checklist
Checkpoints for successful migration
7.12 Post-Migration Tasks
Deactivate old plugin CRON, update plugin config to Remote Mode
SECTION 8 - TENANCY, BILLING & USER ACCESS MODEL
Goal: Define multi-tenant access, workspace isolation, role permissions, and subscription billing.
8.1 Core Concepts
- Tenant: Logical workspace
- User: Authenticated account inside tenant
- Subscription: Stripe-linked billing plan
- Workspace: UI grouping under tenant
- Site Integration: Connected WordPress instances
8.2 Data Model Overview
Django Models: Tenant, User, Plan, Subscription
8.3 Stripe Integration Workflow
6-step process from plan selection to webhook handling
8.4 Credit System Logic
Credit costs per action:
- Keyword clustering: 1 credit / 30 keywords
- Content idea generation: 1 credit / idea
- Full blog content: 3 credits
- AI image generation: 1 credit / image
- Reparse content: 1 credit
- Auto publish: Free if already generated
8.5 Roles & Access Permissions
Owner, Admin, Editor, Viewer roles with specific permissions
8.6 Tenant Isolation Enforcement
Database-level, file-level, API-level, worker-level isolation
8.7 Tenant Dashboard Layout (Frontend)
React components for tenant management
8.8 Billing Plans (Finalized Baseline)
- Free/Trial: $1, 25 credits, 1 user, 1 site
- Starter: $39, 200 credits, 3 users, 3 sites
- Pro: $89, 600 credits, 5 users, 5 sites
- Agency: $199, 2000 credits, 15 users, 10 sites
- Custom/Enterprise: Variable
8.9 Webhooks for Sync
Stripe and WordPress webhook handlers
8.10 Suspended Tenant Behavior
Handling payment failures
8.11 Multi-Tenant CRON Scheduler
Per-tenant queue system
8.12 Cross-Module Tenant Integration
How each module enforces tenant boundaries
8.13 Stripe Integration Files
Backend implementation structure
8.14 Security Enforcement
Authentication, authorization, webhook validation, file access, admin audit
8.15 Example Flow - New Tenant Signup
Complete signup to activation workflow
SECTION 9 - AI AUTOMATION PIPELINE & TASK ENGINE
Goal: Establish unified, tenant-aware automation system for all AI-based tasks.
9.1 Core Design Principles
- Single-Item Execution
- Tenant Isolation
- Unified Scheduler
- Configurable Limits
- Recoverable Jobs
9.2 AI Task Types & Flow
- Planner: Keyword Clustering → Idea Generation
- Writer: AI Draft Generation → Reparse → Publish
- Thinker: Prompt Creation / Persona / Strategy
- Image Generator: DALL-E / Runware image tasks
- Linker (Phase-2): Backlink planner
9.3 Queue Architecture (Redis-Backed)
Redis 7.x with Celery or django-q, tenant-based queue naming
9.4 AI Execution Workflow
Scheduler → Redis Queue → Django Worker → AI Engine API → Parser Layer → DB + Logs
9.5 Execution Limits & Global Settings
Parameters for AI_MAX_ITEMS_PER_REQUEST, batch sizes, timeouts, retries, cost caps
9.6 Task Lifecycle (Writer Example)
Queued → Dispatch → AI Request → Response Handling → Validation → Storage → Optional Actions
9.7 CRON Automation Flows
- cron_auto_cluster(): 6h
- cron_auto_ideas(): 6h
- cron_auto_writer(): 3h
- cron_auto_image(): 3h
- cron_auto_publish(): 12h
- cron_cleanup_logs(): 24h
9.8 AI Model Routing Logic
Model selector with auto-balancing and fallback
9.9 AI Pipeline Directory Map
Backend module structure for AI pipeline
9.10 Credit Deduction Example
Credit deduction logic
9.11 Error Recovery Flow
Handling timeouts, invalid JSON, parser failures, sync failures, credit errors
9.12 Frontend Task Control (React Components)
TaskQueueTable, TaskControlPanel, AutomationConfigForm with WebSocket feed
9.13 Monitoring & Telemetry
Structured logging with tenant, task_type, status, duration, model, credits_spent, output_size
9.14 Local AI Dev Mode (Cursor-Ready)
Development configuration for local testing
9.15 Verification Checklist
Checkpoints for queue, worker, task enqueue, credit deduction, AI response, CRON logs
9.16 Future Enhancements
Parallel pipelines, semantic caching, rate shaping, cost anomaly alerts, tracing integration
SECTION 10 - SECURITY, LOGGING & MONITORING FRAMEWORK
Goal: Define full-stack security, audit, and observability framework.
10.1 Security Architecture Overview
Layers: Frontend, Backend, Database, AI Layer, Infrastructure, Integrations
10.2 Authentication & Token System
JWT authentication with tenant context injection, role verification, session expiry
10.3 Authorization & Role-Based Rules
Role access scope definitions
10.4 Secure API Architecture
Endpoint pattern, JWT verification, tenant match, role access, input validation
10.5 Stripe & Webhook Security
HMAC validation for webhooks
10.6 Data Encryption & Storage Policy
Encryption for credentials/keys, storage policies for AI responses, files, backups, logs
10.7 API Rate Limiting
Per tenant (30 req/min), per IP (100 req/min), per worker (1 AI request/iteration), per model (60 req/min)
10.8 Logging System Overview
Centralized logging: App → Django Logger → Postgres → Loki Stream → Grafana
10.9 Monitoring & Alerts Stack
Tools: Portainer, Loki, Grafana, Alertmanager, Redis Inspector
Custom alerts for AI timeout, failed tasks, Stripe failures, CRON lag
10.10 Data Backup & Recovery
Backup frequencies and retention:
- Postgres DB: Every 6 hours, 7 days retention
- FileBrowser/Media: Daily, 14 days
- Caddy/Config: Daily, 7 days
- Logs (Loki): Rolling window, 30 days
10.11 Error & Exception Handling
Handling for AI API errors, DB write errors, worker crashes, CRON failures, user API errors
10.12 Developer Audit Trail
Critical system events logged in igny8_audit with 1 year retention
10.13 Local Dev & Security Mirrors
Development configuration with AI_DEV_MODE, CORS settings, mock data
10.14 Security Verification Checklist
Checkpoints for HTTPS, JWT validation, tenant isolation, webhook verification, file access, backups, Redis security, network isolation, rate limiting
10.15 Future Enhancements
OAuth 2.0 login, tenant-level dashboards, ElasticSearch sink, offsite sync, smart anomaly detection
10.16 Final Summary
Security model fully container-aware and tenant-isolated
Logging & metrics unified under Loki + Grafana
Stripe billing, AI cost tracking, and access control audited
Developer-friendly dev mode supported
Production deployment validated under current Docker infrastructure
END OF DOCUMENT

View File

@@ -0,0 +1,308 @@
# TROUBLESHOOTING: Converting to Blocks and Image Shortcode Injection
**Date:** October 26, 2025
**Session Duration:** Extended debugging session
**Issue:** AI-generated content failing to save due to malformed heading blocks and shortcode injection failures
**Status:****RESOLVED**
---
## 🚨 **PROBLEM SUMMARY**
The AI content generation pipeline was failing at the post creation stage due to:
1. **Malformed Heading Blocks**: `igny8_convert_to_wp_blocks()` was creating heading blocks without required `level` attributes
2. **Shortcode Injection Failure**: `insert_igny8_shortcode_blocks_into_blocks()` was skipping all heading blocks due to missing level attributes
3. **Complete Pipeline Failure**: Post creation was aborting when shortcode injection failed
4. **Incorrect Task Status**: Tasks were being marked as 'failed' instead of staying in draft status
### **Debug Log Evidence:**
```
[26-Oct-2025 11:23:00 UTC] IGNY8 BLOCKS: Skipping heading block #4 — missing 'level' attribute
[26-Oct-2025 11:23:00 UTC] IGNY8 BLOCKS: Skipping heading block #18 — missing 'level' attribute
[26-Oct-2025 11:23:00 UTC] IGNY8 BLOCKS: Skipping heading block #36 — missing 'level' attribute
[26-Oct-2025 11:23:00 UTC] IGNY8 BLOCKS: Skipping heading block #54 — missing 'level' attribute
[26-Oct-2025 11:23:00 UTC] IGNY8 BLOCKS: ❌ Shortcode injection failed — no blocks found after serialization
[26-Oct-2025 11:23:00 UTC] IGNY8 DEBUG - Shortcode injection failed: No shortcodes found in parsed blocks
[26-Oct-2025 11:23:00 UTC] IGNY8 DEBUG: I AM ACTIVE AND RUNNING IN AJAX.PHP - igny8_create_post_from_ai_response() returned: false
```
---
## 🔍 **ROOT CAUSE ANALYSIS**
### **Primary Issue: Missing Level Attributes**
The `igny8_convert_to_wp_blocks()` function was generating heading blocks like this:
```php
// WRONG - Missing level attribute
"<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">Title</h2>\n<!-- /wp:heading -->"
```
Instead of:
```php
// CORRECT - With level attribute
"<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">Title</h2>\n<!-- /wp:heading -->"
```
### **Secondary Issue: No Fallback Logic**
When shortcode injection failed, the entire post creation process was aborted with `return false`, preventing any content from being saved.
### **Tertiary Issue: Incorrect Task Status Management**
Tasks were being marked as 'failed' when post creation failed, instead of remaining in draft status for retry.
---
## 🛠️ **SOLUTIONS IMPLEMENTED**
### **1. Fixed Block Conversion Function**
**File:** `core/admin/ajax.php` (lines 4722-4727)
**Problem:** H2 headings missing level attributes
```php
// BEFORE (WRONG)
if (preg_match('/^<h([2])[^>]*>(.*?)<\/h\1>$/is', $block, $m)) {
$blocks[] = "<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">{$m[2]}</h2>\n<!-- /wp:heading -->";
}
```
**Solution:** Added level attribute to all heading blocks
```php
// AFTER (FIXED)
if (preg_match('/^<h([2])[^>]*>(.*?)<\/h\1>$/is', $block, $m)) {
$blocks[] = "<!-- wp:heading {\"level\":2} -->\n<h2 class=\"wp-block-heading\">{$m[2]}</h2>\n<!-- /wp:heading -->";
}
```
### **2. Added Block Structure Validation**
**File:** `ai/modules-ai.php` (lines 1795-1827)
**New Function:** `igny8_validate_and_fix_blocks()`
```php
function igny8_validate_and_fix_blocks($block_content) {
if (empty($block_content)) {
return $block_content;
}
$blocks = parse_blocks($block_content);
$fixed_blocks = [];
foreach ($blocks as $index => $block) {
// Fix heading blocks missing level attribute
if (($block['blockName'] ?? null) === 'core/heading') {
$level = $block['attrs']['level'] ?? null;
if ($level === null) {
// Try to extract level from innerHTML
$inner_html = $block['innerHTML'] ?? '';
if (preg_match('/<h([1-6])[^>]*>/i', $inner_html, $matches)) {
$detected_level = intval($matches[1]);
$block['attrs']['level'] = $detected_level;
error_log("IGNY8 BLOCKS: Fixed heading block #$index - detected level $detected_level from innerHTML");
} else {
// Default to H2 if we can't detect
$block['attrs']['level'] = 2;
error_log("IGNY8 BLOCKS: Fixed heading block #$index - defaulted to level 2");
}
}
}
$fixed_blocks[] = $block;
}
return serialize_blocks($fixed_blocks);
}
```
### **3. Enhanced Shortcode Injection Debug Logging**
**File:** `ai/modules-ai.php` (lines 1862-1904)
**Added:** Comprehensive debug logging
```php
error_log("IGNY8 BLOCKS: Parsed " . count($blocks) . " total blocks");
foreach ($blocks as $index => $block) {
if (($block['blockName'] ?? null) === 'core/heading') {
$heading_blocks_found++;
$level = $block['attrs']['level'] ?? null;
error_log("IGNY8 BLOCKS: Heading block #$index - level: " . ($level ?? 'NULL') . ", innerHTML: " . substr($block['innerHTML'] ?? '', 0, 50) . "...");
if ($level !== 2) {
if ($level === null) {
error_log("IGNY8 BLOCKS: Skipping heading block #$index — missing 'level' attribute");
} else {
error_log("IGNY8 BLOCKS: Skipping heading block #$index — level $level (not H2)");
}
continue;
}
$valid_h2_blocks++;
// ... injection logic
}
}
error_log("IGNY8 BLOCKS: Summary - Total headings: $heading_blocks_found, Valid H2s: $valid_h2_blocks, Shortcodes injected: $injected");
```
### **4. Implemented Fallback Logic for Post Creation**
**File:** `ai/modules-ai.php` (lines 1203-1210, 1221-1225)
**Problem:** Post creation failed completely when shortcode injection failed
```php
// BEFORE (WRONG)
if (!$has_shortcode) {
error_log("IGNY8 DEBUG - Shortcode injection failed: No shortcodes found in parsed blocks");
igny8_log_ai_event('Shortcode Injection Failed', 'writer', 'content_generation', 'error', 'No shortcodes found after injection', 'Editor type: ' . $editor_type);
return false; // ← This aborted the entire process
}
```
**Solution:** Added fallback logic to continue without shortcodes
```php
// AFTER (FIXED)
if (!$has_shortcode) {
error_log("IGNY8 DEBUG - Shortcode injection failed: No shortcodes found in parsed blocks");
igny8_log_ai_event('Shortcode Injection Failed', 'writer', 'content_generation', 'warning', 'No shortcodes found after injection - proceeding without shortcodes', 'Editor type: ' . $editor_type);
// FALLBACK: Continue with post creation without shortcodes
$content = $final_block_content;
} else {
$content = $final_block_content;
}
```
### **5. Fixed Task Status Management**
**File:** `core/admin/ajax.php` (lines 1968-1972)
**Problem:** Tasks marked as 'failed' when post creation failed
```php
// BEFORE (WRONG)
} else {
// Update task status to failed if post creation failed
$wpdb->update(
$wpdb->prefix . 'igny8_tasks',
[
'status' => 'failed', // ← Wrong!
'updated_at' => current_time('mysql')
],
['id' => $task_id],
['%s', '%s'],
['%d']
);
}
```
**Solution:** Removed status change on failure
```php
// AFTER (FIXED)
} else {
// Log failure but DO NOT change task status - keep it as draft
igny8_log_ai_event('WordPress Post Creation Failed', 'writer', 'content_generation', 'error', 'Failed to create WordPress post from AI content', 'Task ID: ' . $task_id);
igny8_log_ai_event('AI Content Generation Failed', 'writer', 'content_generation', 'error', 'Content generation failed - post creation unsuccessful', 'Task ID: ' . $task_id);
}
```
---
## 📊 **BEFORE vs AFTER COMPARISON**
### **Before (Broken Pipeline):**
1. ❌ HTML → Blocks (missing level attributes)
2. ❌ Block Validation (skipped)
3. ❌ Shortcode Injection (failed - no valid H2s)
4. ❌ Post Creation (aborted with `return false`)
5. ❌ Task Status (set to 'failed')
### **After (Fixed Pipeline):**
1. ✅ HTML → Blocks (with proper level attributes)
2. ✅ Block Validation (auto-fixes malformed blocks)
3. ✅ Shortcode Injection (works with valid H2s)
4. ✅ Post Creation (succeeds with or without shortcodes)
5. ✅ Task Status (only changes on success)
---
## 🔧 **TECHNICAL DETAILS**
### **Files Modified:**
- `core/admin/ajax.php` - Fixed block conversion function
- `ai/modules-ai.php` - Added validation, enhanced logging, implemented fallbacks
### **New Functions Added:**
- `igny8_validate_and_fix_blocks()` - Block structure validation and repair
### **Functions Enhanced:**
- `igny8_convert_to_wp_blocks()` - Added level attributes to headings
- `insert_igny8_shortcode_blocks_into_blocks()` - Enhanced debug logging
- `igny8_create_post_from_ai_response()` - Added fallback logic
### **Debug Logging Added:**
- Block conversion progress
- Heading block analysis (level, content preview)
- Shortcode injection statistics
- Fallback warnings instead of errors
---
## 🎯 **RESOLUTION VERIFICATION**
### **Expected Debug Logs (After Fix):**
```
[26-Oct-2025 11:23:00 UTC] IGNY8 BLOCKS: Parsed 25 total blocks
[26-Oct-2025 11:23:00 UTC] IGNY8 BLOCKS: Heading block #4 - level: 2, innerHTML: <h2>Section Title</h2>...
[26-Oct-2025 11:23:00 UTC] IGNY8 BLOCKS: Heading block #18 - level: 2, innerHTML: <h2>Another Section</h2>...
[26-Oct-2025 11:23:00 UTC] IGNY8 BLOCKS: Injecting shortcode after H2 #2: [igny8-image id="desktop-2"] [igny8-image id="mobile-2"]
[26-Oct-2025 11:23:00 UTC] IGNY8 BLOCKS: Summary - Total headings: 4, Valid H2s: 4, Shortcodes injected: 3
[26-Oct-2025 11:23:00 UTC] IGNY8 DEBUG: I AM ACTIVE AND RUNNING IN AJAX.PHP - igny8_create_post_from_ai_response() returned: 123
```
### **Success Criteria Met:**
- ✅ All heading blocks have proper level attributes
- ✅ Shortcode injection works correctly
- ✅ Posts are created successfully
- ✅ Task status only changes on success
- ✅ Comprehensive debug logging available
- ✅ Fallback behavior when shortcodes fail
---
## 🚀 **LESSONS LEARNED**
### **Key Insights:**
1. **Block Structure Validation is Critical**: Gutenberg blocks require specific attributes to function properly
2. **Fallback Logic is Essential**: Pipeline should continue even when optional features fail
3. **Debug Logging is Invaluable**: Detailed logging helps identify issues quickly
4. **Task Status Management**: Only change status on actual success, not on partial failures
### **Best Practices Established:**
1. Always validate block structure before processing
2. Implement fallback logic for non-critical features
3. Use comprehensive debug logging for complex pipelines
4. Separate critical failures from optional feature failures
5. Maintain task status integrity for retry scenarios
---
## 📋 **MAINTENANCE NOTES**
### **Future Considerations:**
- Monitor debug logs for any new block structure issues
- Consider adding more robust HTML parsing for edge cases
- May need to enhance shortcode injection for different content types
- Keep fallback logic in mind for future pipeline additions
### **Testing Recommendations:**
- Test with various HTML content structures
- Verify shortcode injection with different heading patterns
- Confirm fallback behavior works correctly
- Monitor task status changes in production
---
**Session Completed:** October 26, 2025
**Status:****FULLY RESOLVED**
**Next Steps:** Monitor production logs and verify stability

View File

@@ -0,0 +1,14 @@
<?php
/**
* ==============================
* 📁 Folder Scope Declaration
* ==============================
* Folder: /docs/
* Purpose: Markdown docs only
* Rules:
* - Must contain only markdown files
* - Documentation and guides only
* - No executable code allowed
* - User and developer documentation
* - Architecture and API references
*/

View File

@@ -0,0 +1,24 @@
Add-Type -AssemblyName System.IO.Compression.FileSystem
$docxPath = "e:\GitHub\igny8-ai-seo\docs\Igny8 WP Plugin to Igny8 App Migration Plan.docx"
$zip = [System.IO.Compression.ZipFile]::OpenRead($docxPath)
$entry = $zip.Entries | Where-Object { $_.FullName -eq "word/document.xml" }
if ($entry) {
$stream = $entry.Open()
$reader = New-Object System.IO.StreamReader($stream)
$xml = $reader.ReadToEnd()
$reader.Close()
$stream.Close()
# Extract text from XML (simple approach)
$xml = $xml -replace '<[^>]+>', "`n"
$xml = $xml -replace '\s+', " "
$xml = $xml -replace '\n\s*\n', "`n"
Write-Output $xml.Trim()
}
$zip.Dispose()

View File

@@ -0,0 +1,485 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : sync-ajax.php
* @location : /flows/sync-ajax.php
* @type : AJAX Handler
* @scope : Global
* @allowed : AJAX endpoints, automation handlers, workflow operations
* @reusability : Globally Reusable
* @notes : Automation-specific AJAX handlers for all workflows
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Hook handlers for cluster metrics updates
*/
// Hook for when keywords are added
// Hook definitions moved to sync-hooks.php
/**
* Handle keyword cluster updates
*/
function igny8_handle_keyword_cluster_update($record_id, $cluster_id) {
if ($cluster_id) {
try {
igny8_update_cluster_metrics($cluster_id);
} catch (Exception $e) {
error_log("ERROR in igny8_handle_keyword_cluster_update: " . $e->getMessage());
}
}
}
/**
* AJAX handler for keyword imports with workflow automation
*/
// Hook moved to sync-hooks.php
function igny8_ajax_import_keywords() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce')) {
wp_send_json_error('Security check failed');
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
$keywords_data = $_POST['keywords'] ?? [];
if (empty($keywords_data) || !is_array($keywords_data)) {
wp_send_json_error('No keywords data provided');
}
global $wpdb;
$imported_ids = [];
try {
// Import keywords
foreach ($keywords_data as $keyword_data) {
$sanitized_data = [
'keyword' => sanitize_text_field($keyword_data['keyword'] ?? ''),
'search_volume' => intval($keyword_data['search_volume'] ?? 0),
'difficulty' => sanitize_text_field($keyword_data['difficulty'] ?? ''),
'cpc' => floatval($keyword_data['cpc'] ?? 0),
'intent' => sanitize_text_field($keyword_data['intent'] ?? ''),
'status' => 'unmapped',
'created_at' => current_time('mysql'),
'updated_at' => current_time('mysql')
];
// Skip empty keywords
if (empty($sanitized_data['keyword'])) {
continue;
}
$result = $wpdb->insert(
$wpdb->prefix . 'igny8_keywords',
$sanitized_data,
['%s', '%d', '%s', '%f', '%s', '%s', '%s', '%s']
);
if ($result !== false) {
$imported_ids[] = $wpdb->insert_id;
}
}
if (empty($imported_ids)) {
wp_send_json_error('No keywords were imported');
}
// Trigger workflow automation for imported keywords
$workflow_result = igny8_workflow_triggers('keywords_imported', ['keyword_ids' => $imported_ids]);
// Prepare response
$response = [
'message' => 'Keywords imported successfully',
'imported_count' => count($imported_ids),
'keyword_ids' => $imported_ids
];
if ($workflow_result && $workflow_result['success']) {
$response['workflow_message'] = $workflow_result['message'];
if (isset($workflow_result['clusters_created'])) {
$response['workflow_data'] = [
'clusters_created' => $workflow_result['clusters_created'],
'cluster_ids' => $workflow_result['cluster_ids'] ?? []
];
}
}
wp_send_json_success($response);
} catch (Exception $e) {
wp_send_json_error('Import error: ' . $e->getMessage());
}
}
/**
* AJAX handler for creating a single task from an idea
*/
// Hook moved to sync-hooks.php
function igny8_create_task_from_idea_ajax() {
try {
check_ajax_referer('igny8_ajax_nonce', 'nonce');
if (!current_user_can('edit_posts')) {
wp_send_json_error(['message' => 'Unauthorized']);
}
$idea_id = isset($_POST['idea_id']) ? absint($_POST['idea_id']) : 0;
if (!$idea_id) {
wp_send_json_error(['message' => 'Invalid idea id']);
}
$result = igny8_create_task_from_idea($idea_id);
wp_send_json($result);
} catch (Exception $e) {
wp_send_json_error(['message' => 'Error: ' . $e->getMessage()]);
}
}
/**
* AJAX handler for bulk creating tasks from ideas
*/
// Hook moved to sync-hooks.php
function igny8_bulk_create_tasks_from_ideas_ajax() {
try {
check_ajax_referer('igny8_ajax_nonce', 'nonce');
if (!current_user_can('edit_posts')) {
wp_send_json_error(['message' => 'Unauthorized']);
}
$ids = isset($_POST['idea_ids']) ? array_map('absint', (array)$_POST['idea_ids']) : [];
$ids = array_values(array_filter($ids));
if (empty($ids)) {
wp_send_json_error(['message' => 'No idea ids provided']);
}
// Check if ideas have status other than 'new'
global $wpdb;
$placeholders = implode(',', array_fill(0, count($ids), '%d'));
$ideas_status = $wpdb->get_results($wpdb->prepare("
SELECT id, idea_title, status FROM {$wpdb->prefix}igny8_content_ideas
WHERE id IN ({$placeholders}) AND status != 'new'
", $ids));
if (!empty($ideas_status)) {
$idea_titles = array_column($ideas_status, 'idea_title');
wp_send_json_error(['message' => 'Ideas are not in "new" status: ' . implode(', ', array_slice($idea_titles, 0, 3)) . (count($idea_titles) > 3 ? '...' : '')]);
}
$created = [];
$skipped = [];
$failed = [];
foreach ($ids as $id) {
$res = igny8_create_task_from_idea($id);
if (!empty($res['success'])) {
if (!empty($res['task_id'])) {
$created[] = $res['task_id'];
} else {
$skipped[] = $id;
}
} else {
$failed[] = $id;
}
}
// Update metrics once per unique idea id to be safe
foreach ($ids as $id) {
igny8_update_idea_metrics($id);
}
wp_send_json_success([
'created' => $created,
'skipped' => $skipped,
'failed' => $failed,
'message' => sprintf('Created %d, skipped %d, failed %d', count($created), count($skipped), count($failed))
]);
} catch (Exception $e) {
wp_send_json_error(['message' => 'Error: ' . $e->getMessage()]);
}
}
/**
* Helper function to create a task from an idea
*
* @param int $idea_id The idea ID to create task from
* @return array Result array with success status and message
*/
function igny8_create_task_from_idea($idea_id) {
global $wpdb;
$idea = $wpdb->get_row($wpdb->prepare(
"SELECT id, idea_title, idea_description, content_structure, content_type, keyword_cluster_id, estimated_word_count, target_keywords
FROM {$wpdb->prefix}igny8_content_ideas WHERE id=%d",
$idea_id
));
if (!$idea) {
return ['success' => false, 'message' => 'Idea not found'];
}
// Optional dedupe policy: One open task per idea
$existing = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM {$wpdb->prefix}igny8_tasks WHERE idea_id=%d AND status IN ('draft','queued','in_progress','review') LIMIT 1",
$idea_id
));
if ($existing) {
return ['success' => true, 'message' => 'Task already exists for this idea', 'task_id' => (int)$existing];
}
$ins = $wpdb->insert($wpdb->prefix . 'igny8_tasks', [
'title' => $idea->idea_title,
'description' => $idea->idea_description,
'content_structure' => $idea->content_structure ?: 'cluster_hub',
'content_type' => $idea->content_type ?: 'post',
'cluster_id' => $idea->keyword_cluster_id ?: null,
'priority' => 'medium',
'status' => 'queued',
'idea_id' => (int)$idea_id,
'keywords' => $idea->target_keywords ?: '',
'word_count' => $idea->estimated_word_count ?: 0,
'schedule_at' => null,
'assigned_post_id' => null,
'created_at' => current_time('mysql'),
'updated_at' => current_time('mysql')
], ['%s', '%s', '%s', '%s', '%d', '%s', '%s', '%d', '%s', '%d', '%s', '%d', '%s', '%s']);
if ($ins === false) {
return ['success' => false, 'message' => 'Failed to create task'];
}
$task_id = (int)$wpdb->insert_id;
// Update idea status to 'scheduled' when successfully queued to writer
$wpdb->update(
$wpdb->prefix . 'igny8_content_ideas',
['status' => 'scheduled'],
['id' => $idea_id],
['%s'],
['%d']
);
// Update keyword status to 'queued' when task is created from idea
if ($idea->keyword_cluster_id) {
// Get all keywords in this cluster and update their status to 'queued'
$keyword_ids = $wpdb->get_col($wpdb->prepare("
SELECT id FROM {$wpdb->prefix}igny8_keywords
WHERE cluster_id = %d
", $idea->keyword_cluster_id));
if (!empty($keyword_ids)) {
$placeholders = implode(',', array_fill(0, count($keyword_ids), '%d'));
$wpdb->query($wpdb->prepare("
UPDATE {$wpdb->prefix}igny8_keywords
SET status = 'queued'
WHERE id IN ({$placeholders})
", $keyword_ids));
}
}
igny8_update_idea_metrics($idea_id);
igny8_write_log('queue_to_writer', ['idea_id' => $idea_id, 'task_id' => $task_id]);
return ['success' => true, 'message' => "Task queued for Writer", "task_id" => $task_id];
}
/**
* AJAX handler for bulk deleting keywords
*/
// Hook moved to sync-hooks.php
function igny8_ajax_bulk_delete_keywords() {
// Verify nonce for security
if (!check_ajax_referer('igny8_ajax_nonce', 'nonce', true)) {
wp_send_json_error(['message' => 'Security check failed.']);
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'You do not have permission to perform this action.']);
}
// Get parameters
$keyword_ids = $_POST['keyword_ids'] ?? [];
if (empty($keyword_ids) || !is_array($keyword_ids)) {
wp_send_json_error(['message' => 'No keywords selected for deletion.']);
}
// Call bulk delete function
$result = igny8_bulk_delete_keywords($keyword_ids);
if ($result['success']) {
wp_send_json_success([
'message' => $result['message'],
'deleted_count' => $result['deleted_count']
]);
} else {
wp_send_json_error(['message' => $result['message']]);
}
}
/**
* AJAX handler for bulk mapping keywords to cluster
*/
// Hook moved to sync-hooks.php
function igny8_ajax_bulk_map_keywords() {
// Verify nonce for security
if (!check_ajax_referer('igny8_ajax_nonce', 'nonce', true)) {
wp_send_json_error(['message' => 'Security check failed.']);
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'You do not have permission to perform this action.']);
}
// Get parameters
$keyword_ids = $_POST['keyword_ids'] ?? [];
$cluster_id = intval($_POST['cluster_id'] ?? 0);
if (empty($keyword_ids) || !is_array($keyword_ids)) {
wp_send_json_error(['message' => 'No keywords selected for mapping.']);
}
if (empty($cluster_id)) {
wp_send_json_error(['message' => 'No cluster selected for mapping.']);
}
// Call bulk map function
$result = igny8_bulk_map_keywords($keyword_ids, $cluster_id);
if ($result['success']) {
wp_send_json_success([
'message' => $result['message'],
'mapped_count' => $result['mapped_count']
]);
} else {
wp_send_json_error(['message' => $result['message']]);
}
}
/**
* AJAX handler for bulk unmapping keywords from clusters
*/
// Hook moved to sync-hooks.php
function igny8_ajax_bulk_unmap_keywords() {
// Verify nonce for security
if (!check_ajax_referer('igny8_ajax_nonce', 'nonce', true)) {
wp_send_json_error(['message' => 'Security check failed.']);
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'You do not have permission to perform this action.']);
}
// Get parameters
$keyword_ids = $_POST['keyword_ids'] ?? [];
if (empty($keyword_ids) || !is_array($keyword_ids)) {
wp_send_json_error(['message' => 'No keywords selected for unmapping.']);
}
// Call bulk unmap function
$result = igny8_bulk_unmap_keywords($keyword_ids);
if ($result['success']) {
wp_send_json_success([
'message' => $result['message'],
'unmapped_count' => $result['unmapped_count']
]);
} else {
wp_send_json_error(['message' => $result['message']]);
}
}
/**
* AJAX handler for bulk deleting records
*/
// Hook moved to sync-hooks.php
function igny8_delete_bulk_records() {
// Verify nonce
if (!wp_verify_nonce($_POST['nonce'], 'igny8_ajax_nonce')) {
wp_send_json_error('Security check failed');
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error('Insufficient permissions');
}
// Get parameters
$table_id = sanitize_text_field($_POST['table_id'] ?? '');
$record_ids = $_POST['record_ids'] ?? [];
if (empty($table_id) || empty($record_ids) || !is_array($record_ids)) {
wp_send_json_error('Table ID and Record IDs required');
}
// Get table name
$table_name = igny8_get_table_name($table_id);
if (!$table_name) {
wp_send_json_error('Invalid table ID');
}
// Sanitize IDs
$record_ids = array_map('intval', $record_ids);
$record_ids = array_filter($record_ids, function($id) { return $id > 0; });
if (empty($record_ids)) {
wp_send_json_error('No valid record IDs provided');
}
global $wpdb;
// Handle cluster deletion - clean up keyword relationships
if ($table_id === 'planner_clusters') {
// Before deleting clusters, unmap all keywords from these clusters
$placeholders = implode(',', array_fill(0, count($record_ids), '%d'));
$unmapped_count = $wpdb->query($wpdb->prepare(
"UPDATE {$wpdb->prefix}igny8_keywords
SET cluster_id = NULL, status = 'unmapped', updated_at = CURRENT_TIMESTAMP
WHERE cluster_id IN ({$placeholders})",
$record_ids
));
if ($unmapped_count !== false) {
// Log the unmapping
error_log("Igny8: Unmapped {$unmapped_count} keywords from deleted clusters: " . implode(',', $record_ids));
}
}
// Build placeholders for IN clause
$placeholders = implode(',', array_fill(0, count($record_ids), '%d'));
try {
$result = $wpdb->query($wpdb->prepare(
"DELETE FROM `{$table_name}` WHERE id IN ({$placeholders})",
$record_ids
));
if ($result === false) {
wp_send_json_error('Failed to delete records');
}
wp_send_json_success([
'message' => "Successfully deleted {$result} record(s)",
'deleted_count' => $result
]);
} catch (Exception $e) {
wp_send_json_error('Database error: ' . $e->getMessage());
}
}

View File

@@ -0,0 +1,673 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : sync-functions.php
* @location : /flows/sync-functions.php
* @type : Function Library
* @scope : Global
* @allowed : Automation logic, workflow functions, sync operations
* @reusability : Globally Reusable
* @notes : Core automation functions for all workflows
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Update cluster metrics based on related keywords and mappings
*/
function igny8_update_cluster_metrics($cluster_id) {
global $wpdb;
if (!$cluster_id) {
return ['success' => false, 'message' => 'Operation failed'];
}
try {
// Get keyword count and aggregated data
$keyword_data = $wpdb->get_row($wpdb->prepare("
SELECT
COUNT(*) as keyword_count,
COALESCE(SUM(search_volume), 0) as total_volume,
COALESCE(AVG(difficulty), 0) as avg_difficulty
FROM {$wpdb->prefix}igny8_keywords
WHERE cluster_id = %d
", $cluster_id));
// Get mapped pages count from taxonomy relationships
$cluster_term_id = $wpdb->get_var($wpdb->prepare("
SELECT cluster_term_id FROM {$wpdb->prefix}igny8_clusters WHERE id = %d
", $cluster_id));
$mapped_pages_count = 0;
if ($cluster_term_id) {
// Count content (posts, pages, products) associated with this cluster term
$mapped_pages_count = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(DISTINCT object_id)
FROM {$wpdb->prefix}term_relationships tr
INNER JOIN {$wpdb->prefix}term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
WHERE tt.term_id = %d AND tt.taxonomy = 'clusters'
", $cluster_term_id));
}
// Update cluster record
$wpdb->update(
$wpdb->prefix . 'igny8_clusters',
[
'keyword_count' => intval($keyword_data->keyword_count),
'total_volume' => intval($keyword_data->total_volume),
'avg_difficulty' => floatval($keyword_data->avg_difficulty),
'mapped_pages_count' => intval($mapped_pages_count)
],
['id' => $cluster_id],
['%d', '%d', '%f', '%d'],
['%d']
);
return true;
} catch (Exception $e) {
error_log("ERROR in igny8_update_cluster_metrics: " . $e->getMessage());
return ['success' => false, 'message' => 'Failed to update cluster metrics: ' . $e->getMessage()];
}
}
/**
* Update campaign metrics
*/
function igny8_update_campaign_metrics($campaign_id) {
global $wpdb;
if (!$campaign_id) {
return ['success' => false, 'message' => 'Operation failed'];
}
// Get campaign performance data
$performance_data = $wpdb->get_row($wpdb->prepare("
SELECT
COUNT(*) as total_tasks,
COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed_tasks,
COALESCE(AVG(seo_score), 0) as avg_seo_score
FROM {$wpdb->prefix}igny8_tasks
WHERE campaign_id = %d
", $campaign_id));
// Update campaign record
$wpdb->update(
$wpdb->prefix . 'igny8_campaigns',
[
'total_tasks' => intval($performance_data->total_tasks),
'completed_tasks' => intval($performance_data->completed_tasks),
'avg_seo_score' => floatval($performance_data->avg_seo_score)
],
['id' => $campaign_id],
['%d', '%d', '%f'],
['%d']
);
return true;
}
/**
* Update idea metrics based on related tasks
*/
function igny8_update_idea_metrics($idea_id) {
global $wpdb;
if (!$idea_id) {
return ['success' => false, 'message' => 'Operation failed'];
}
// Get tasks count for this idea
$tasks_count = $wpdb->get_var($wpdb->prepare("
SELECT COUNT(*)
FROM {$wpdb->prefix}igny8_tasks
WHERE idea_id = %d
", $idea_id));
// Update idea record
$wpdb->update(
$wpdb->prefix . 'igny8_content_ideas',
[
'tasks_count' => intval($tasks_count)
],
['id' => $idea_id],
['%d'],
['%d']
);
return true;
}
/**
* Update task metrics based on related variations
*/
// REMOVED: Task variations functionality - tasks don't need variations
/**
* Update keyword status when WordPress post is published
*
* @param int $post_id The WordPress post ID that was published
*/
function igny8_update_keywords_on_post_publish($post_id) {
global $wpdb;
if (!$post_id) {
return;
}
// Find tasks that are assigned to this post
$tasks = $wpdb->get_results($wpdb->prepare("
SELECT id, cluster_id
FROM {$wpdb->prefix}igny8_tasks
WHERE assigned_post_id = %d AND cluster_id IS NOT NULL
", $post_id));
if (empty($tasks)) {
return;
}
// Update keyword status to 'mapped' for all keywords in the clusters of published tasks
foreach ($tasks as $task) {
if ($task->cluster_id) {
// Get all keywords in this cluster and update their status to 'mapped'
$keyword_ids = $wpdb->get_col($wpdb->prepare("
SELECT id FROM {$wpdb->prefix}igny8_keywords
WHERE cluster_id = %d
", $task->cluster_id));
if (!empty($keyword_ids)) {
$placeholders = implode(',', array_fill(0, count($keyword_ids), '%d'));
$wpdb->query($wpdb->prepare("
UPDATE {$wpdb->prefix}igny8_keywords
SET status = 'mapped'
WHERE id IN ({$placeholders})
", $keyword_ids));
}
// Update task status to 'completed'
$wpdb->update(
$wpdb->prefix . 'igny8_tasks',
['status' => 'completed'],
['id' => $task->id],
['%s'],
['%d']
);
}
}
// Log the event
if (function_exists('igny8_write_log')) {
igny8_write_log('post_published', [
'post_id' => $post_id,
'tasks_updated' => count($tasks),
'message' => 'Keywords updated to mapped status'
]);
}
}
/**
* Automatically create clusters from keywords using AI clustering
*
* @param array $keyword_ids Optional array of keyword IDs to cluster. If empty, uses all unmapped keywords.
* @return array Result array with success status and message
*/
function igny8_auto_create_clusters_from_keywords($keyword_ids = []) {
global $wpdb;
// If no keyword IDs provided, get all unmapped keywords
if (empty($keyword_ids)) {
$keyword_ids = $wpdb->get_col("
SELECT id FROM {$wpdb->prefix}igny8_keywords
WHERE cluster_id IS NULL
");
}
if (empty($keyword_ids)) {
return ['success' => true, 'message' => 'No unmapped keywords found for clustering'];
}
// Limit to 20 keywords for AI processing
if (count($keyword_ids) > 20) {
$keyword_ids = array_slice($keyword_ids, 0, 20);
}
// Get keywords data
$placeholders = implode(',', array_fill(0, count($keyword_ids), '%d'));
$keywords = $wpdb->get_results($wpdb->prepare("
SELECT * FROM {$wpdb->prefix}igny8_keywords
WHERE id IN ({$placeholders})
", $keyword_ids));
if (empty($keywords)) {
return ['success' => false, 'message' => 'No valid keywords found for clustering'];
}
// Create clusters using AI (this would call the AI clustering function)
// For now, create a simple cluster with all keywords
$cluster_name = igny8_generate_cluster_name_from_keywords(array_column($keywords, 'keyword'));
// Create cluster
$cluster_result = $wpdb->insert(
$wpdb->prefix . 'igny8_clusters',
[
'cluster_name' => $cluster_name,
'status' => 'active',
'created_at' => current_time('mysql')
],
['%s', '%s', '%s']
);
if ($cluster_result === false) {
return ['success' => false, 'message' => 'Failed to create cluster'];
}
$cluster_id = $wpdb->insert_id;
// Trigger cluster_added action to create taxonomy term
do_action('igny8_cluster_added', $cluster_id);
// Map all keywords to this cluster
$mapped_count = $wpdb->query($wpdb->prepare("
UPDATE {$wpdb->prefix}igny8_keywords
SET cluster_id = %d, status = 'mapped'
WHERE id IN ({$placeholders})
", array_merge([$cluster_id], $keyword_ids)));
// Update cluster metrics
igny8_update_cluster_metrics($cluster_id);
return [
'success' => true,
'message' => "Created cluster '{$cluster_name}' with {$mapped_count} keywords",
'cluster_id' => $cluster_id,
'mapped_count' => $mapped_count
];
}
/**
* Workflow automation trigger system
*/
function igny8_workflow_triggers($event, $payload = []) {
switch ($event) {
case 'keywords_imported':
if (isset($payload['keyword_ids'])) {
return igny8_auto_create_clusters_from_keywords($payload['keyword_ids']);
}
break;
case 'cluster_created':
if (isset($payload['cluster_id'])) {
// Create ideas from cluster
$ideas_result = igny8_auto_create_ideas_from_clusters($payload['cluster_id']);
return [
'success' => true,
'message' => 'Cluster workflow completed',
'ideas' => $ideas_result
];
}
break;
}
return ['success' => true, 'message' => 'No workflow automation for this event'];
}
/**
* Bulk delete keywords with cluster metrics update
*/
function igny8_bulk_delete_keywords($keyword_ids) {
global $wpdb;
if (empty($keyword_ids) || !is_array($keyword_ids)) {
return ['success' => false, 'message' => 'Operation failed'];
}
// Get cluster IDs before deletion for metrics update
$placeholders = implode(',', array_fill(0, count($keyword_ids), '%d'));
$cluster_ids = $wpdb->get_col($wpdb->prepare("
SELECT DISTINCT cluster_id
FROM {$wpdb->prefix}igny8_keywords
WHERE id IN ({$placeholders})
AND cluster_id IS NOT NULL
", $keyword_ids));
// Delete keywords
$result = $wpdb->query($wpdb->prepare("
DELETE FROM {$wpdb->prefix}igny8_keywords
WHERE id IN ({$placeholders})
", $keyword_ids));
if ($result !== false) {
// Update cluster metrics for affected clusters
foreach ($cluster_ids as $cluster_id) {
if ($cluster_id) {
igny8_update_cluster_metrics($cluster_id);
}
}
return $result;
}
return ['success' => false, 'message' => 'Operation failed'];
}
/**
* Bulk map keywords to cluster
*
* @param array $keyword_ids Array of keyword IDs to map
* @param int $cluster_id Cluster ID to map keywords to
* @return array ['success' => bool, 'message' => string, 'mapped_count' => int]
*/
function igny8_bulk_map_keywords($keyword_ids, $cluster_id) {
global $wpdb;
if (empty($keyword_ids) || !is_array($keyword_ids)) {
return ['success' => false, 'message' => 'No keywords selected for mapping', 'mapped_count' => 0];
}
if (empty($cluster_id) || !is_numeric($cluster_id)) {
return ['success' => false, 'message' => 'Invalid cluster ID provided', 'mapped_count' => 0];
}
$cluster_id = intval($cluster_id);
// Verify cluster exists
$cluster_exists = $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}igny8_clusters WHERE id = %d",
$cluster_id
));
if (!$cluster_exists) {
return ['success' => false, 'message' => 'Cluster not found', 'mapped_count' => 0];
}
// Sanitize and validate IDs
$keyword_ids = array_map('intval', $keyword_ids);
$keyword_ids = array_filter($keyword_ids, function($id) { return $id > 0; });
if (empty($keyword_ids)) {
return ['success' => false, 'message' => 'No valid keyword IDs provided', 'mapped_count' => 0];
}
// Get old cluster IDs for metrics update
$placeholders = implode(',', array_fill(0, count($keyword_ids), '%d'));
$old_cluster_ids = $wpdb->get_col($wpdb->prepare(
"SELECT DISTINCT cluster_id FROM {$wpdb->prefix}igny8_keywords WHERE id IN ({$placeholders}) AND cluster_id IS NOT NULL",
$keyword_ids
));
// Update keywords to new cluster
$mapped_count = $wpdb->query($wpdb->prepare(
"UPDATE {$wpdb->prefix}igny8_keywords SET cluster_id = %d, status = 'mapped', updated_at = CURRENT_TIMESTAMP WHERE id IN ({$placeholders})",
array_merge([$cluster_id], $keyword_ids)
));
if ($mapped_count === false) {
return ['success' => false, 'message' => 'Failed to map keywords to cluster', 'mapped_count' => 0];
}
// Update metrics for old clusters (they lost keywords)
if (!empty($old_cluster_ids)) {
foreach (array_unique($old_cluster_ids) as $old_cluster_id) {
if ($old_cluster_id && $old_cluster_id != $cluster_id) {
igny8_update_cluster_metrics($old_cluster_id);
}
}
}
// Update metrics for new cluster (gained keywords)
igny8_update_cluster_metrics($cluster_id);
return [
'success' => true,
'message' => "Successfully mapped {$mapped_count} keyword(s) to cluster",
'mapped_count' => $mapped_count
];
}
/**
* Bulk unmap keywords from their clusters
*
* @param array $keyword_ids Array of keyword IDs to unmap
* @return array ['success' => bool, 'message' => string, 'unmapped_count' => int]
*/
function igny8_bulk_unmap_keywords($keyword_ids) {
global $wpdb;
if (empty($keyword_ids) || !is_array($keyword_ids)) {
return ['success' => false, 'message' => 'No keywords selected for unmapping', 'unmapped_count' => 0];
}
// Sanitize and validate IDs
$keyword_ids = array_map('intval', $keyword_ids);
$keyword_ids = array_filter($keyword_ids, function($id) { return $id > 0; });
if (empty($keyword_ids)) {
return ['success' => false, 'message' => 'No valid keyword IDs provided', 'unmapped_count' => 0];
}
// Get cluster IDs before unmapping for metrics update
$placeholders = implode(',', array_fill(0, count($keyword_ids), '%d'));
$cluster_ids = $wpdb->get_col($wpdb->prepare(
"SELECT DISTINCT cluster_id FROM {$wpdb->prefix}igny8_keywords WHERE id IN ({$placeholders}) AND cluster_id IS NOT NULL",
$keyword_ids
));
// Update keywords to unmap them
$unmapped_count = $wpdb->query($wpdb->prepare(
"UPDATE {$wpdb->prefix}igny8_keywords SET cluster_id = NULL, status = 'unmapped', updated_at = CURRENT_TIMESTAMP WHERE id IN ({$placeholders})",
$keyword_ids
));
if ($unmapped_count === false) {
return ['success' => false, 'message' => 'Failed to unmap keywords from clusters', 'unmapped_count' => 0];
}
// Update metrics for affected clusters (they lost keywords)
if (!empty($cluster_ids)) {
foreach (array_unique($cluster_ids) as $cluster_id) {
if ($cluster_id) {
igny8_update_cluster_metrics($cluster_id);
}
}
}
return [
'success' => true,
'message' => "Successfully unmapped {$unmapped_count} keyword(s) from clusters",
'unmapped_count' => $unmapped_count
];
}
/**
* Automatically create or link a taxonomy term when a new cluster is created.
*
* @param int $cluster_id The cluster ID from wp_igny8_clusters.
*/
function igny8_auto_create_cluster_term($cluster_id) {
global $wpdb;
// 1. Validate cluster
$cluster = $wpdb->get_row($wpdb->prepare(
"SELECT cluster_name, cluster_term_id FROM {$wpdb->prefix}igny8_clusters WHERE id = %d",
$cluster_id
));
if (!$cluster) {
error_log("Igny8: Cluster not found for ID $cluster_id.");
return;
}
// 2. Skip if already mapped
if (!empty($cluster->cluster_term_id)) {
error_log("Igny8: Cluster $cluster_id already linked to term {$cluster->cluster_term_id}.");
return;
}
// 3. Ensure taxonomy exists
if (!taxonomy_exists('clusters') && function_exists('igny8_register_taxonomies')) {
igny8_register_taxonomies();
}
// 4. Create or find existing term
$term_result = wp_insert_term(
sanitize_text_field($cluster->cluster_name),
'clusters',
['slug' => sanitize_title($cluster->cluster_name)]
);
if (is_wp_error($term_result)) {
// If term exists, fetch it
$existing = get_term_by('name', $cluster->cluster_name, 'clusters');
$term_id = $existing ? $existing->term_id : 0;
} else {
$term_id = $term_result['term_id'];
}
// 5. Save term ID back to cluster table
if ($term_id > 0) {
$wpdb->update(
"{$wpdb->prefix}igny8_clusters",
['cluster_term_id' => $term_id],
['id' => $cluster_id],
['%d'],
['%d']
);
error_log("Igny8: Created and linked taxonomy term $term_id for cluster $cluster_id.");
} else {
error_log("Igny8: Failed to link taxonomy term for cluster $cluster_id.");
}
}
/**
* Automatically update taxonomy term when a cluster name is changed/updated.
*
* @param int $cluster_id The cluster ID from wp_igny8_clusters.
*/
function igny8_auto_update_cluster_term($cluster_id) {
global $wpdb;
// 1. Validate cluster
$cluster = $wpdb->get_row($wpdb->prepare(
"SELECT cluster_name, cluster_term_id FROM {$wpdb->prefix}igny8_clusters WHERE id = %d",
$cluster_id
));
if (!$cluster) {
error_log("Igny8: Cluster not found for ID $cluster_id.");
return;
}
// 2. Skip if no term linked
if (empty($cluster->cluster_term_id)) {
error_log("Igny8: Cluster $cluster_id has no linked taxonomy term.");
return;
}
// 3. Ensure taxonomy exists
if (!taxonomy_exists('clusters') && function_exists('igny8_register_taxonomies')) {
igny8_register_taxonomies();
}
// 4. Get the existing term
$term = get_term($cluster->cluster_term_id, 'clusters');
if (is_wp_error($term) || !$term) {
error_log("Igny8: Taxonomy term {$cluster->cluster_term_id} not found for cluster $cluster_id.");
return;
}
// 5. Check if name has changed
if ($term->name === $cluster->cluster_name) {
error_log("Igny8: Cluster $cluster_id name unchanged, skipping taxonomy update.");
return;
}
// 6. Update the term name and slug
$update_result = wp_update_term(
$cluster->cluster_term_id,
'clusters',
[
'name' => sanitize_text_field($cluster->cluster_name),
'slug' => sanitize_title($cluster->cluster_name)
]
);
if (is_wp_error($update_result)) {
error_log("Igny8: Failed to update taxonomy term for cluster $cluster_id: " . $update_result->get_error_message());
} else {
error_log("Igny8: Successfully updated taxonomy term {$cluster->cluster_term_id} for cluster $cluster_id.");
}
}
// Hook registration for automatic cluster term creation
// Hook moved to sync-hooks.php
/**
* Handle content association/disassociation with cluster taxonomy terms
*/
function igny8_handle_content_cluster_association($object_id, $terms, $tt_ids, $taxonomy) {
// Only process clusters taxonomy
if ($taxonomy !== 'clusters') {
return;
}
global $wpdb;
// Get all cluster IDs that have terms in this taxonomy update
$cluster_ids = [];
foreach ($terms as $term) {
if (is_numeric($term)) {
$term_id = $term;
} else {
$term_obj = get_term_by('slug', $term, 'clusters');
$term_id = $term_obj ? $term_obj->term_id : null;
}
if ($term_id) {
// Find cluster that has this term_id
$cluster_id = $wpdb->get_var($wpdb->prepare("
SELECT id FROM {$wpdb->prefix}igny8_clusters
WHERE cluster_term_id = %d
", $term_id));
if ($cluster_id) {
$cluster_ids[] = $cluster_id;
}
}
}
// Update metrics for all affected clusters
foreach (array_unique($cluster_ids) as $cluster_id) {
igny8_update_cluster_metrics($cluster_id);
}
}
// Hook registration for automatic cluster metrics updates when content is associated/disassociated with cluster terms
// Hook moved to sync-hooks.php
/**
* Update task metrics including word count and meta sync
*/
function igny8_update_task_metrics($task_id) {
global $wpdb;
$post_id = $wpdb->get_var($wpdb->prepare("SELECT assigned_post_id FROM {$wpdb->prefix}igny8_tasks WHERE id = %d", $task_id));
if ($post_id) {
$content = get_post_field('post_content', $post_id);
$word_count = str_word_count(strip_tags($content));
update_post_meta($post_id, '_igny8_word_count', $word_count);
$wpdb->update("{$wpdb->prefix}igny8_tasks", ['word_count' => $word_count], ['id' => $task_id]);
}
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : sync-hooks.php
* @location : /flows/sync-hooks.php
* @type : Function Library
* @scope : Global
* @allowed : Hook definitions, workflow registration, automation triggers
* @reusability : Globally Reusable
* @notes : Central hook definitions for all automation workflows
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// ===================================================================
// AUTOMATION WORKFLOW HOOKS
// ===================================================================
/**
* Keyword lifecycle hooks - trigger cluster updates
*/
add_action('igny8_keyword_added', 'igny8_handle_keyword_cluster_update', 10, 2);
add_action('igny8_keyword_updated', 'igny8_handle_keyword_cluster_update', 10, 2);
add_action('igny8_keyword_deleted', 'igny8_handle_keyword_cluster_update', 10, 2);
/**
* Cluster management hooks
*/
add_action('igny8_cluster_added', 'igny8_auto_create_cluster_term', 10, 1);
add_action('igny8_cluster_updated', 'igny8_auto_update_cluster_term', 10, 1);
add_action('set_object_terms', 'igny8_handle_content_cluster_association', 10, 4);
/**
* WordPress post publishing hooks - update keyword status when content is published
*/
add_action('publish_post', 'igny8_update_keywords_on_post_publish', 10, 1);
add_action('publish_page', 'igny8_update_keywords_on_post_publish', 10, 1);
add_action('draft_to_publish', 'igny8_update_keywords_on_post_publish', 10, 1);
add_action('future_to_publish', 'igny8_update_keywords_on_post_publish', 10, 1);
// ===================================================================
// AJAX WORKFLOW HOOKS
// ===================================================================
/**
* Import and bulk operations
*/
add_action('wp_ajax_igny8_import_keywords', 'igny8_ajax_import_keywords');
/**
* Planner → Writer bridge operations
*/
add_action('wp_ajax_igny8_create_task_from_idea', 'igny8_create_task_from_idea_ajax');
add_action('wp_ajax_igny8_bulk_create_tasks_from_ideas', 'igny8_bulk_create_tasks_from_ideas_ajax');
/**
* Bulk keyword operations
*/
add_action('wp_ajax_igny8_bulk_delete_keywords', 'igny8_ajax_bulk_delete_keywords');
add_action('wp_ajax_igny8_bulk_map_keywords', 'igny8_ajax_bulk_map_keywords');
add_action('wp_ajax_igny8_bulk_unmap_keywords', 'igny8_ajax_bulk_unmap_keywords');
/**
* Bulk record operations
*/
add_action('wp_ajax_igny8_delete_bulk_records', 'igny8_delete_bulk_records');
// ===================================================================
// AI QUEUE PROCESSING - DEPRECATED (MOVED TO CRON MANAGER)
// ===================================================================
// Note: AI queue processing is now handled by the smart automation system
// in core/cron/igny8-cron-master-dispatcher.php and core/cron/igny8-cron-handlers.php
// ===================================================================
// AI AUTOMATION CRON JOBS - DEPRECATED (MOVED TO CRON MANAGER)
// ===================================================================
// Note: All automation cron jobs are now handled by the smart automation system
// in core/cron/igny8-cron-master-dispatcher.php and core/cron/igny8-cron-handlers.php
// All cron handlers have been moved to core/cron/igny8-cron-handlers.php
// ===================================================================
// WRITER MODULE HOOKS
// ===================================================================
/**
* Hook into draft creation and recalculate metrics
*/
add_action('igny8_task_draft_created', function($task_id, $post_id) {
igny8_update_task_metrics($task_id);
}, 10, 2);

View File

@@ -0,0 +1,384 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : igny8-wp-load-handler.php
* @location : /igny8-wp-load-handler.php
* @type : CRON Handler
* @scope : Global
* @allowed : CRON processing, automation triggers, background tasks
* @reusability : Globally Reusable
* @notes : WordPress load handler for CRON requests
*/
// Only process if this is an Igny8 CRON request
if (isset($_GET['import_id']) && $_GET['import_id'] === 'igny8_cron') {
// Log: CRON request started
echo "<div style='background:#f0f0f0;padding:10px;margin:5px;border:1px solid #ccc;'>";
echo "<strong>Igny8 CRON: Request started</strong><br>";
error_log("Igny8 CRON: Request started - " . date('Y-m-d H:i:s'));
// Force load Igny8 plugin if not already loaded
echo "<strong>Igny8 CRON: Checking if plugin is active</strong><br>";
if (!function_exists('igny8_get_ai_setting')) {
echo "<strong>Igny8 CRON: Plugin not loaded, attempting to load</strong><br>";
// Try to load the plugin manually - check multiple possible locations
$possible_paths = [
'igny8-ai-seo/igny8.php',
'igny8/igny8.php',
'igny8-ai-seo.php'
];
$plugin_loaded = false;
foreach ($possible_paths as $plugin_file) {
$full_path = WP_PLUGIN_DIR . '/' . $plugin_file;
echo "<strong>Igny8 CRON: Checking path:</strong> " . $full_path . "<br>";
if (file_exists($full_path)) {
echo "<strong>Igny8 CRON: Plugin file found at:</strong> " . $full_path . ", loading<br>";
include_once $full_path;
$plugin_loaded = true;
break;
}
}
if (!$plugin_loaded) {
echo "<strong>Igny8 CRON: Plugin file not found in any expected location</strong><br>";
echo "<strong>Igny8 CRON: WP_PLUGIN_DIR:</strong> " . WP_PLUGIN_DIR . "<br>";
echo "<strong>Igny8 CRON: Available plugins:</strong> " . implode(', ', array_diff(scandir(WP_PLUGIN_DIR), array('.', '..'))) . "<br>";
}
// Check again after manual load
if (!function_exists('igny8_get_ai_setting')) {
echo "<strong>Igny8 CRON: Plugin still not active after manual load, trying WordPress plugin loading</strong><br>";
// Try to trigger WordPress plugin loading
if (function_exists('do_action')) {
echo "<strong>Igny8 CRON: Triggering plugins_loaded action</strong><br>";
do_action('plugins_loaded');
}
// Manually load cron handlers if still not found
if (!function_exists('igny8_auto_cluster_cron_handler')) {
echo "<strong>Igny8 CRON: Manually loading cron handlers</strong><br>";
$cron_handlers_path = WP_PLUGIN_DIR . '/igny8-ai-seo/core/cron/igny8-cron-handlers.php';
if (file_exists($cron_handlers_path)) {
echo "<strong>Igny8 CRON: Loading cron handlers from:</strong> " . $cron_handlers_path . "<br>";
include_once $cron_handlers_path;
} else {
echo "<strong>Igny8 CRON: Cron handlers file not found at:</strong> " . $cron_handlers_path . "<br>";
}
}
// Final check
if (!function_exists('igny8_get_ai_setting')) {
echo "<strong>Igny8 CRON: Plugin still not active after all attempts</strong><br>";
error_log("Igny8 CRON: Plugin not active - igny8_get_ai_setting function not found");
http_response_code(500);
die(json_encode(['error' => 'Igny8 plugin not active']));
}
}
}
echo "<strong>Igny8 CRON: Plugin is active</strong><br>";
// Ensure cron handlers are loaded
if (!function_exists('igny8_auto_cluster_cron_handler')) {
echo "<strong>Igny8 CRON: Loading cron handlers manually</strong><br>";
$cron_handlers_path = WP_PLUGIN_DIR . '/igny8-ai-seo/core/cron/igny8-cron-handlers.php';
if (file_exists($cron_handlers_path)) {
echo "<strong>Igny8 CRON: Loading cron handlers from:</strong> " . $cron_handlers_path . "<br>";
include_once $cron_handlers_path;
} else {
echo "<strong>Igny8 CRON: Cron handlers file not found at:</strong> " . $cron_handlers_path . "<br>";
}
}
echo "<strong>Igny8 CRON: Cron handlers loaded</strong><br>";
// Load global helpers for sector options function
if (!function_exists('igny8_get_sector_options')) {
echo "<strong>Igny8 CRON: Loading global helpers for sector options</strong><br>";
$global_helpers_path = WP_PLUGIN_DIR . '/igny8-ai-seo/core/admin/global-helpers.php';
if (file_exists($global_helpers_path)) {
echo "<strong>Igny8 CRON: Loading global helpers from:</strong> " . $global_helpers_path . "<br>";
include_once $global_helpers_path;
} else {
echo "<strong>Igny8 CRON: Global helpers file not found at:</strong> " . $global_helpers_path . "<br>";
}
}
echo "<strong>Igny8 CRON: Global helpers loaded</strong><br>";
// Load AJAX handlers for clustering function
if (!function_exists('igny8_ajax_ai_cluster_keywords')) {
echo "<strong>Igny8 CRON: Loading AJAX handlers for clustering</strong><br>";
$ajax_handlers_path = WP_PLUGIN_DIR . '/igny8-ai-seo/core/admin/ajax.php';
if (file_exists($ajax_handlers_path)) {
echo "<strong>Igny8 CRON: Loading AJAX handlers from:</strong> " . $ajax_handlers_path . "<br>";
include_once $ajax_handlers_path;
} else {
echo "<strong>Igny8 CRON: AJAX handlers file not found at:</strong> " . $ajax_handlers_path . "<br>";
}
}
echo "<strong>Igny8 CRON: AJAX handlers loaded</strong><br>";
// Load model rates configuration to prevent PHP warnings
echo "<strong>Igny8 CRON: Loading model rates configuration</strong><br>";
$model_rates_path = WP_PLUGIN_DIR . '/igny8-ai-seo/ai/model-rates-config.php';
if (file_exists($model_rates_path)) {
echo "<strong>Igny8 CRON: Loading model rates from:</strong> " . $model_rates_path . "<br>";
include_once $model_rates_path;
// Verify the global variable is set
if (isset($GLOBALS['IGNY8_MODEL_RATES'])) {
echo "<strong>Igny8 CRON: Model rates global variable loaded successfully</strong><br>";
} else {
echo "<strong>Igny8 CRON: WARNING - Model rates global variable not set</strong><br>";
}
} else {
echo "<strong>Igny8 CRON: Model rates file not found at:</strong> " . $model_rates_path . "<br>";
}
echo "<strong>Igny8 CRON: Model rates loaded</strong><br>";
// Load database functions for taxonomy registration
if (!function_exists('igny8_register_taxonomies')) {
echo "<strong>Igny8 CRON: Loading database functions for taxonomy registration</strong><br>";
$db_functions_path = WP_PLUGIN_DIR . '/igny8-ai-seo/core/db/db.php';
if (file_exists($db_functions_path)) {
echo "<strong>Igny8 CRON: Loading database functions from:</strong> " . $db_functions_path . "<br>";
include_once $db_functions_path;
} else {
echo "<strong>Igny8 CRON: Database functions file not found at:</strong> " . $db_functions_path . "<br>";
}
}
echo "<strong>Igny8 CRON: Database functions loaded</strong><br>";
// Load master dispatcher for smart automation
if (!function_exists('igny8_master_dispatcher_run')) {
echo "<strong>Igny8 CRON: Loading master dispatcher</strong><br>";
$master_dispatcher_path = WP_PLUGIN_DIR . '/igny8-ai-seo/core/cron/igny8-cron-master-dispatcher.php';
if (file_exists($master_dispatcher_path)) {
echo "<strong>Igny8 CRON: Loading master dispatcher from:</strong> " . $master_dispatcher_path . "<br>";
include_once $master_dispatcher_path;
} else {
echo "<strong>Igny8 CRON: Master dispatcher file not found at:</strong> " . $master_dispatcher_path . "<br>";
}
}
echo "<strong>Igny8 CRON: Master dispatcher loaded</strong><br>";
// 🔧 PATCH: Enable full WordPress taxonomy and rewrite support in CRON context
echo "<strong>Igny8 CRON: Initializing WordPress rewrite and taxonomy system</strong><br>";
// Set globals for rewrite + taxonomy system
global $wp_rewrite, $wp_taxonomies;
// ✅ Fix: Initialize rewrite system (prevents "add_rewrite_tag() on null" error)
if (!isset($wp_rewrite)) {
echo "<strong>Igny8 CRON: Initializing WP_Rewrite system</strong><br>";
$wp_rewrite = new WP_Rewrite();
$wp_rewrite->init();
echo "<strong>Igny8 CRON: WP_Rewrite system initialized</strong><br>";
} else {
echo "<strong>Igny8 CRON: WP_Rewrite system already initialized</strong><br>";
}
// ✅ Trigger key WP hooks (needed for taxonomy registration) - but be selective
echo "<strong>Igny8 CRON: Triggering WordPress hooks for taxonomy support</strong><br>";
// Only trigger plugins_loaded (safest for external cron)
if (!did_action('plugins_loaded')) {
do_action('plugins_loaded');
echo "<strong>Igny8 CRON: plugins_loaded hook triggered</strong><br>";
} else {
echo "<strong>Igny8 CRON: plugins_loaded hook already executed</strong><br>";
}
// Skip after_setup_theme and init to avoid widget system issues
echo "<strong>Igny8 CRON: Skipping after_setup_theme and init hooks to avoid widget system conflicts</strong><br>";
// ✅ Load and register Igny8 taxonomies manually
echo "<strong>Igny8 CRON: Registering Igny8 taxonomies</strong><br>";
if (function_exists('igny8_register_taxonomies')) {
// Ensure WordPress global taxonomies array exists
global $wp_taxonomies;
if (!is_array($wp_taxonomies)) {
$wp_taxonomies = [];
echo "<strong>Igny8 CRON: Initialized wp_taxonomies global array</strong><br>";
}
igny8_register_taxonomies();
echo "<strong>Igny8 CRON: Igny8 taxonomies registered</strong><br>";
} else {
echo "<strong>Igny8 CRON: WARNING - igny8_register_taxonomies function not found</strong><br>";
}
// ✅ Verify taxonomy registration
if (!taxonomy_exists('clusters')) {
echo "<strong>Igny8 CRON: ❌ Taxonomy 'clusters' not registered</strong><br>";
error_log('[CRON] ❌ Taxonomy "clusters" not registered');
// Fallback: Try to register clusters taxonomy directly
echo "<strong>Igny8 CRON: Attempting direct taxonomy registration as fallback</strong><br>";
if (function_exists('register_taxonomy')) {
register_taxonomy('clusters', ['post', 'page', 'product'], [
'hierarchical' => true,
'labels' => [
'name' => 'Content Clusters',
'singular_name' => 'Cluster',
],
'public' => true,
'show_ui' => true,
'show_admin_column' => true,
'show_in_nav_menus' => true,
'show_tagcloud' => true,
'show_in_rest' => true,
]);
echo "<strong>Igny8 CRON: Direct clusters taxonomy registration attempted</strong><br>";
}
} else {
echo "<strong>Igny8 CRON: ✅ Taxonomy 'clusters' registered successfully</strong><br>";
error_log('[CRON] ✅ Taxonomy "clusters" registered successfully');
}
if (!taxonomy_exists('sectors')) {
echo "<strong>Igny8 CRON: ❌ Taxonomy 'sectors' not registered</strong><br>";
error_log('[CRON] ❌ Taxonomy "sectors" not registered');
// Fallback: Try to register sectors taxonomy directly
if (function_exists('register_taxonomy')) {
register_taxonomy('sectors', ['post', 'page', 'product'], [
'hierarchical' => true,
'labels' => [
'name' => 'Sectors',
'singular_name' => 'Sector',
],
'public' => true,
'show_ui' => true,
'show_admin_column' => true,
'show_in_nav_menus' => true,
'show_tagcloud' => true,
'show_in_rest' => true,
]);
echo "<strong>Igny8 CRON: Direct sectors taxonomy registration attempted</strong><br>";
}
} else {
echo "<strong>Igny8 CRON: ✅ Taxonomy 'sectors' registered successfully</strong><br>";
error_log('[CRON] ✅ Taxonomy "sectors" registered successfully');
}
// === STEP 1: Validate Security Key ===
echo "<strong>Igny8 CRON: Validating security key</strong><br>";
$provided_key = isset($_GET['import_key']) ? sanitize_text_field($_GET['import_key']) : '';
$stored_key = get_option('igny8_secure_cron_key');
echo "<strong>Igny8 CRON: Provided key:</strong> " . substr($provided_key, 0, 8) . "...<br>";
echo "<strong>Igny8 CRON: Stored key:</strong> " . substr($stored_key, 0, 8) . "...<br>";
if (empty($stored_key) || $provided_key !== $stored_key) {
echo "<strong>Igny8 CRON: Security key validation failed</strong><br>";
error_log("Igny8 CRON: Security key validation failed - provided: " . substr($provided_key, 0, 8) . ", stored: " . substr($stored_key, 0, 8));
status_header(403);
echo json_encode(['error' => 'Invalid or missing security key']);
exit;
}
echo "<strong>Igny8 CRON: Security key validated</strong><br>";
// === STEP 2: Capture Action ===
echo "<strong>Igny8 CRON: Capturing action parameter</strong><br>";
$action = isset($_GET['action']) ? sanitize_text_field($_GET['action']) : '';
echo "<strong>Igny8 CRON: Action:</strong> " . $action . "<br>";
if (empty($action)) {
echo "<strong>Igny8 CRON: Missing action parameter</strong><br>";
error_log("Igny8 CRON: Missing action parameter");
status_header(400);
echo json_encode(['error' => 'Missing action parameter']);
exit;
}
// === STEP 3: Execute CRON Function ===
echo "<strong>Igny8 CRON: Building allowed actions list</strong><br>";
$allowed_actions = [
'master_scheduler' => 'igny8_master_dispatcher_run',
'auto_cluster' => 'igny8_auto_cluster_cron',
'auto_ideas' => 'igny8_auto_generate_ideas_cron',
'auto_queue' => 'igny8_auto_queue_cron',
'auto_content' => 'igny8_auto_generate_content_cron',
'auto_images' => 'igny8_auto_generate_images_cron',
'auto_publish' => 'igny8_auto_publish_drafts_cron',
'auto_optimizer' => 'igny8_auto_optimizer_cron',
'auto_recalc' => 'igny8_auto_recalc_cron',
'health_check' => 'igny8_health_check_cron',
'test' => 'igny8_test_cron_endpoint',
];
echo "<strong>Igny8 CRON: Allowed actions:</strong> " . implode(', ', array_keys($allowed_actions)) . "<br>";
if (!array_key_exists($action, $allowed_actions)) {
echo "<strong>Igny8 CRON: Invalid action name:</strong> " . $action . "<br>";
error_log("Igny8 CRON: Invalid action name: " . $action);
status_header(400);
echo json_encode(['error' => 'Invalid action name']);
exit;
}
echo "<strong>Igny8 CRON: Action validated:</strong> " . $action . "<br>";
// Execute safely and catch errors
echo "<strong>Igny8 CRON: Starting execution</strong><br>";
try {
// Handle test action specially
if ($action === 'test') {
echo "<strong>Igny8 CRON: Executing test action</strong><br>";
status_header(200);
echo json_encode([
'success' => true,
'message' => 'Igny8 CRON endpoint is working via wp-load.php',
'timestamp' => date('Y-m-d H:i:s'),
'server' => $_SERVER['SERVER_NAME'] ?? 'unknown',
'url_structure' => 'wp-load.php?import_key=[KEY]&import_id=igny8_cron&action=[ACTION]'
]);
exit;
}
echo "<strong>Igny8 CRON: Executing action:</strong> " . $action . "<br>";
echo "<strong>Igny8 CRON: Hook to execute:</strong> " . $allowed_actions[$action] . "<br>";
// Check if the hook function exists
$hook_function = $allowed_actions[$action];
$handler_function = $hook_function . '_handler';
echo "<strong>Igny8 CRON: Checking hook function:</strong> " . $hook_function . "<br>";
echo "<strong>Igny8 CRON: Checking handler function:</strong> " . $handler_function . "<br>";
if (!function_exists($hook_function) && !function_exists($handler_function)) {
echo "<strong>Igny8 CRON: Neither hook nor handler function found</strong><br>";
echo "<strong>Igny8 CRON: Available functions containing 'igny8_auto_cluster':</strong><br>";
$functions = get_defined_functions()['user'];
foreach ($functions as $func) {
if (strpos($func, 'igny8_auto_cluster') !== false) {
echo "- " . $func . "<br>";
}
}
error_log("Igny8 CRON: Hook function not found: " . $hook_function);
status_header(500);
echo json_encode(['error' => 'Hook function not found', 'hook' => $hook_function]);
exit;
}
echo "<strong>Igny8 CRON: Hook function exists:</strong> " . $allowed_actions[$action] . "<br>";
// Execute the hook
echo "<strong>Igny8 CRON: Calling do_action(" . $allowed_actions[$action] . ")</strong><br>";
do_action($allowed_actions[$action]);
echo "<strong>Igny8 CRON: do_action completed successfully</strong><br>";
status_header(200);
echo json_encode(['success' => true, 'message' => "CRON action '{$action}' executed successfully."]);
} catch (Throwable $e) {
echo "<strong>Igny8 CRON: Exception caught:</strong> " . $e->getMessage() . "<br>";
error_log("Igny8 CRON: Exception caught: " . $e->getMessage() . " in " . $e->getFile() . " line " . $e->getLine());
status_header(500);
echo json_encode(['error' => 'Execution failed', 'details' => $e->getMessage()]);
}
echo "<strong>Igny8 CRON: Request completed</strong><br>";
echo "</div>"; // Close the main div
exit;
}

View File

@@ -0,0 +1,246 @@
<?php
/*
Plugin Name: IGNY8 AI SEO
Description: A comprehensive WordPress plugin for content optimization, automation, and SEO management.
Version: 0.1
Author: Alorig
*/
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : igny8.php
* @location : /igny8.php
* @type : Plugin Bootstrap
* @scope : Global
* @allowed : Plugin initialization, core includes, hook registration
* @reusability : Single Use
* @notes : Main plugin entry point, loads core systems and modules
*/
/**
* ==============================
* 🔷 IGNY8 PLUGIN ROLE INDEX MAP
* ==============================
*
* Layer 1: Global Index (this file)
* Layer 2: Modular Folder Scope
* Layer 3: Subfolder/File Index
* Layer 4: File-Level RULE HEADER
*
* === Folder Role Map ===
*
* /modules/ → Core admin pages (Planner, Writer, etc.)
* /core/ → Layout, init, DB, CRON
* /ai/ → AI content/image logic, parsers, prompt APIs
* /shortcodes/ → All shortcode handler files (split by module)
* /components/ → UI/UX templates (forms, modals, tables)
* /config/ → Central config arrays (filters, tables, prompts)
* /assets/js/ → Module JS files
* /debug/ → Dev-only tools: logs, CRON tests, function runners
* /docs/ → Markdown docs only
*
* Rules:
* - Every .php file must have a RULE HEADER block
* - Only `/core/`, `/ai/`, and `/components/` can be reused globally
* - Modules may not use each other directly
* - No mixed-scope logic allowed
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// ---------------------------------------------------------------------
// PLUGIN INITIALIZATION
// ---------------------------------------------------------------------
add_action('plugins_loaded', 'igny8_bootstrap_plugin');
function igny8_bootstrap_plugin() {
$plugin_path = plugin_dir_path(__FILE__);
// 1. Core database and schema
require_once $plugin_path . 'install.php';
require_once $plugin_path . 'core/db/db.php';
// 2. Database schema is now handled by install.php and db.php
// 2. AI model rates configuration
require_once $plugin_path . 'ai/model-rates-config.php';
// 2. AI prompts library
require_once $plugin_path . 'ai/prompts-library.php';
// 2. Automation workflows (must load early for AJAX)
require_once $plugin_path . 'flows/sync-hooks.php'; // Hook definitions first
require_once $plugin_path . 'flows/sync-functions.php'; // Functions
require_once $plugin_path . 'flows/sync-ajax.php'; // AJAX handlers
// Marker-based image injection removed - now using div container system
// 2.5. Cron system (load after workflows)
// Legacy cron manager removed - using new master dispatcher system
require_once $plugin_path . 'core/cron/igny8-cron-handlers.php'; // Cron handlers
// CRON key generation moved to conditional loading
// Only generate when actually needed (not on every page load)
// Include wp-load handler for CRON requests (after plugin is loaded)
if (isset($_GET['import_id']) && $_GET['import_id'] === 'igny8_cron') {
require_once $plugin_path . 'igny8-wp-load-handler.php';
}
// 3. Core admin functionality
if (is_admin()) {
require_once $plugin_path . 'core/admin/module-manager-class.php'; // Module manager class and functions
require_once $plugin_path . 'core/admin/menu.php';
// Debug utilities (only in admin)
if (defined('WP_DEBUG') && WP_DEBUG) {
// Debug utilities can be added here if needed
}
require_once $plugin_path . 'core/admin/init.php'; // handles admin_init + settings
require_once $plugin_path . 'core/admin/ajax.php'; // registers all AJAX actions
require_once $plugin_path . 'core/admin/meta-boxes.php'; // SEO meta boxes
}
// 4. API layer
require_once $plugin_path . 'ai/openai-api.php';
require_once $plugin_path . 'ai/modules-ai.php';
require_once $plugin_path . 'ai/writer/images/image-generation.php';
// 4.5. Frontend shortcodes (load for both admin and frontend)
require_once $plugin_path . 'shortcodes/ai-shortcodes.php';
require_once $plugin_path . 'shortcodes/writer-shortcodes.php';
// 4. Module components (UI templates)
$components = [
'modules/components/kpi-tpl.php',
'modules/components/filters-tpl.php',
'modules/components/actions-tpl.php',
'modules/components/table-tpl.php',
'modules/components/pagination-tpl.php',
'modules/components/forms-tpl.php'
];
foreach ($components as $file) {
require_once $plugin_path . $file;
}
// 5. Global configuration
$GLOBALS['igny8_kpi_config'] = require_once $plugin_path . 'modules/config/kpi-config.php';
// 6. Register taxonomies
add_action('init', 'igny8_register_taxonomies', 5);
}
/**
* Get or generate CRON key - ensures key is always available
*/
function igny8_get_or_generate_cron_key() {
// Use static cache to prevent multiple database calls
static $cached_key = null;
if ($cached_key !== null) {
return $cached_key;
}
$key = get_option('igny8_secure_cron_key');
if (empty($key)) {
$key = wp_generate_password(32, false, false);
update_option('igny8_secure_cron_key', $key);
}
// Cache the key for this request
$cached_key = $key;
return $key;
}
/**
* Check if current page needs CRON functionality
*/
function igny8_needs_cron_functionality() {
$current_page = $_GET['page'] ?? '';
$current_submodule = $_GET['sm'] ?? '';
// Pages that need CRON functionality
$cron_pages = [
'igny8-schedules', // Schedules page
'igny8-planner' => 'home', // Planner home only
'igny8-writer' => 'home' // Writer home only
];
// Check if current page needs CRON
if (isset($cron_pages[$current_page])) {
if (is_string($cron_pages[$current_page])) {
// Page has submodule requirement
return $current_submodule === $cron_pages[$current_page];
}
return true; // Page needs CRON without submodule requirement
}
return false;
}
// ---------------------------------------------------------------------
// ACTIVATION / DEACTIVATION
// ---------------------------------------------------------------------
register_activation_hook(__FILE__, 'igny8_activate');
register_deactivation_hook(__FILE__, 'igny8_deactivate');
function igny8_activate() {
require_once plugin_dir_path(__FILE__) . 'install.php';
if (function_exists('igny8_install')) {
igny8_install();
}
flush_rewrite_rules();
}
function igny8_deactivate() {
flush_rewrite_rules();
}
// ---------------------------------------------------------------------
// ADMIN ASSETS ENQUEUING
// ---------------------------------------------------------------------
add_action('admin_enqueue_scripts', 'igny8_admin_scripts');
function igny8_admin_scripts($hook) {
// Only load on Igny8 admin pages
if (strpos($hook, 'igny8') === false) return;
$plugin_url = plugin_dir_url(__FILE__);
// Enqueue core CSS with updated version
wp_enqueue_style('igny8-admin-style', $plugin_url . 'assets/css/core.css', [], '0.1');
// Enqueue Chart.js
wp_enqueue_script('chart-js', 'https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/chart.umd.min.js', [], '4.5.0', true);
wp_add_inline_script('chart-js', 'window.Chart = Chart;', 'after');
// Enqueue core JavaScript with dependencies and updated version
wp_enqueue_script('igny8-admin-js', $plugin_url . 'assets/js/core.js', ['jquery', 'chart-js'], '0.1', true);
// Enqueue image queue processor
wp_enqueue_script('igny8-image-queue', $plugin_url . 'assets/js/image-queue-processor.js', ['igny8-admin-js'], '0.1', true);
// AJAX localization
wp_localize_script('igny8-admin-js', 'igny8_ajax', [
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('igny8_ajax_nonce'),
'module' => igny8_get_current_module(),
'debug_enabled' => false
]);
}
// ---------------------------------------------------------------------
// HELPER: Get current module from admin URL
// ---------------------------------------------------------------------
function igny8_get_current_module() {
if (isset($_GET['page']) && strpos($_GET['page'], 'igny8-') === 0) {
return str_replace('igny8-', '', sanitize_text_field($_GET['page']));
}
return 'planner';
}

View File

@@ -0,0 +1,43 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : install.php
* @location : /install.php
* @type : Function Library
* @scope : Global
* @allowed : Plugin installation, database initialization, setup procedures
* @reusability : Single Use
* @notes : Plugin installation and database initialization
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* ------------------------------------------------------------------------
* MAIN INSTALLATION ENTRY
* ------------------------------------------------------------------------
*/
function igny8_install() {
// Load dbDelta if not already loaded
if (!function_exists('dbDelta')) {
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
}
// Load database functions
require_once plugin_dir_path(__FILE__) . 'core/db/db.php';
// Run complete installation using db.php function
if (function_exists('igny8_install_database')) {
igny8_install_database();
}
error_log('Igny8 Compact: Plugin installed successfully');
}
// Register activation hook
register_activation_hook(__FILE__, 'igny8_install');

View File

@@ -0,0 +1,14 @@
<?php
/**
* ==============================
* 📁 Folder Scope Declaration
* ==============================
* Folder: /modules/
* Purpose: Admin UI and logic for core modules (Planner, Writer, etc.)
* Rules:
* - Must not contain reusable code
* - Must use components for forms/tables
* - No AI logic allowed here
* - Each module must be independent
* - No cross-module dependencies allowed
*/

View File

@@ -0,0 +1,44 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : analytics.php
* @location : /modules/analytics/analytics.php
* @type : Admin Page
* @scope : Module Only
* @allowed : Analytics reporting, performance metrics, data visualization
* @reusability : Single Use
* @notes : Analytics reporting page for analytics module
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Handle URL parameters for subpages
$subpage = $_GET['sp'] ?? 'analytics';
// Start output buffering
ob_start();
switch ($subpage) {
case 'analytics':
default:
// Analytics content
?>
<div class="igny8-analytics-page">
<h3>Analytics & Reporting</h3>
<p>Performance analytics and reporting functionality coming soon...</p>
</div>
<?php
break;
}
// Capture page content
$igny8_page_content = ob_get_clean();
// Include global layout
include plugin_dir_path(__FILE__) . '../../core/global-layout.php';
?>

View File

@@ -0,0 +1,14 @@
<?php
/**
* ==============================
* 📁 Folder Scope Declaration
* ==============================
* Folder: /components/
* Purpose: UI/UX templates (forms, modals, tables)
* Rules:
* - Can be reused globally across all modules
* - Contains only UI template components
* - No business logic allowed
* - Must be configuration-driven
* - Pure presentation layer only
*/

View File

@@ -0,0 +1,111 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : actions-tpl.php
* @location : /modules/components/actions-tpl.php
* @type : Component
* @scope : Cross-Module
* @allowed : Action rendering, bulk operations
* @reusability : Shared
* @notes : Dynamic action component for all modules
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Render table actions function
function igny8_render_table_actions($table_id, $actions = []) {
// Set default actions if none provided
$actions = $actions ?: ['export_selected', 'delete_selected', 'export_all', 'import', 'add_new'];
// Check if AI mode is enabled
$ai_mode = igny8_get_ai_setting('planner_mode', 'manual') === 'ai';
// Start output buffering to capture HTML
ob_start();
?>
<!-- Table Actions -->
<div class="igny8-table-actions" data-table="<?php echo esc_attr($table_id); ?>">
<div class="left-actions">
<span id="<?php echo esc_attr($table_id); ?>_count" class="igny8-count-hidden">0 selected</span>
<?php if (in_array('export_selected', $actions)): ?>
<button id="<?php echo esc_attr($table_id); ?>_export_btn" class="igny8-btn igny8-btn-success" disabled>Export Selected</button>
<?php endif; ?>
<?php if (in_array('delete_selected', $actions)): ?>
<button id="<?php echo esc_attr($table_id); ?>_delete_btn" class="igny8-btn igny8-btn-danger" disabled>Delete Selected</button>
<?php endif; ?>
<!-- Publish Selected Button (for Drafts) -->
<?php if ($table_id === 'writer_drafts'): ?>
<button id="<?php echo esc_attr($table_id); ?>_generate_images_btn" class="igny8-btn igny8-btn-accent" disabled>
<span class="dashicons dashicons-format-image"></span> Generate Images
</button>
<button id="<?php echo esc_attr($table_id); ?>_publish_btn" class="igny8-btn igny8-btn-primary" disabled>
<span class="dashicons dashicons-yes-alt"></span> Publish Selected
</button>
<?php endif; ?>
<!-- AI Action Buttons (only visible in AI mode) -->
<?php if ($ai_mode): ?>
<?php if ($table_id === 'planner_keywords' && igny8_get_ai_setting('clustering', 'enabled') === 'enabled'): ?>
<button id="<?php echo esc_attr($table_id); ?>_ai_cluster_btn" class="igny8-btn igny8-btn-accent" disabled>
<span class="dashicons dashicons-admin-generic"></span> Auto Cluster
</button>
<?php endif; ?>
<?php if ($table_id === 'planner_clusters' && igny8_get_ai_setting('ideas', 'enabled') === 'enabled'): ?>
<button id="<?php echo esc_attr($table_id); ?>_ai_ideas_btn" class="igny8-btn igny8-btn-accent" disabled>
<span class="dashicons dashicons-lightbulb"></span> Generate Ideas
</button>
<?php endif; ?>
<?php endif; ?>
<!-- Queue to Writer Button (for Ideas) -->
<?php if ($table_id === 'planner_ideas'): ?>
<button id="<?php echo esc_attr($table_id); ?>_queue_writer_btn" class="igny8-btn igny8-btn-primary" disabled>
<span class="dashicons dashicons-edit"></span> Queue to Writer
</button>
<?php endif; ?>
<?php if ($table_id === 'writer_tasks' && igny8_get_ai_setting('writer_mode', 'manual') === 'ai' && igny8_get_ai_setting('content_generation', 'enabled') === 'enabled'): ?>
<button id="<?php echo esc_attr($table_id); ?>_generate_content_btn" class="igny8-btn igny8-btn-success" disabled>
<span class="dashicons dashicons-admin-generic"></span> Generate Content
</button>
<?php endif; ?>
</div>
<div class="right-actions">
<div class="igny8-ml-auto igny8-flex igny8-flex-gap-10 igny8-align-center">
<?php if (in_array('export_all', $actions)): ?>
<button id="<?php echo esc_attr($table_id); ?>_export_all_btn" class="igny8-btn igny8-btn-outline" onclick="igny8ShowExportModal('<?php echo esc_attr($table_id); ?>')">Export All</button>
<?php endif; ?>
<?php if (in_array('import', $actions)): ?>
<button id="<?php echo esc_attr($table_id); ?>_import_btn" class="igny8-btn igny8-btn-secondary">Import</button>
<?php endif; ?>
<?php if (in_array('add_new', $actions)): ?>
<button
class="igny8-btn igny8-btn-primary"
data-action="addRow"
data-table-id="<?php echo esc_attr($table_id); ?>"
>
Add New
</button>
<?php endif; ?>
</div>
</div>
</div>
<!-- Global notification system is handled by core.js -->
<?php
return ob_get_clean();
}
// Set default values
$table_id = $table_id ?? 'data_table';
$actions = $actions ?? ['export_selected', 'delete_selected', 'export_all', 'import', 'add_new'];
?>

View File

@@ -0,0 +1,77 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : export-modal-tpl.php
* @location : /modules/components/export-modal-tpl.php
* @type : Component
* @scope : Cross-Module
* @allowed : Modal rendering, export functionality
* @reusability : Shared
* @notes : Dynamic export modal component for all modules
*/
// Prevent direct access
if (!defined('ABSPATH') && !defined('IGNY8_INCLUDE_TEMPLATE')) {
exit;
}
// Get configuration for this table
$config = igny8_get_import_export_config($table_id);
if (!$config) {
return;
}
$singular = $config['singular'];
$plural = $config['plural'];
$is_selected = !empty($selected_ids);
$mode_text = $is_selected ? "Selected {$plural}" : "All {$plural}";
?>
<div id="igny8-import-export-modal" class="igny8-modal" data-table-id="<?php echo esc_attr($table_id); ?>">
<div class="igny8-modal-content">
<div class="igny8-modal-header">
<h3>Export <?php echo esc_html($mode_text); ?></h3>
<button class="igny8-btn-close" onclick="igny8CloseImportExportModal()">&times;</button>
</div>
<div class="igny8-modal-body">
<p>Export <?php echo esc_html(strtolower($mode_text)); ?> to CSV format.</p>
<!-- Export Options -->
<div class="igny8-form-group">
<h4>Export Options</h4>
<div class="igny8-checkbox-group">
<label class="igny8-checkbox-label">
<input type="checkbox" id="include-metrics" name="include_metrics" checked>
<span class="igny8-checkbox-text">Include Metrics</span>
</label>
<label class="igny8-checkbox-label">
<input type="checkbox" id="include-relationships" name="include_relationships">
<span class="igny8-checkbox-text">Include Relationships</span>
</label>
<label class="igny8-checkbox-label">
<input type="checkbox" id="include-timestamps" name="include_timestamps">
<span class="igny8-checkbox-text">Include Timestamps</span>
</label>
</div>
</div>
<!-- Hidden form data -->
<form id="igny8-modal-export-form" style="display: none;">
<input type="hidden" name="action" value="<?php echo $is_selected ? 'igny8_export_selected' : 'igny8_run_export'; ?>">
<input type="hidden" name="nonce" value="">
<input type="hidden" name="export_type" value="<?php echo esc_attr($config['type']); ?>">
<?php if ($is_selected): ?>
<input type="hidden" name="selected_ids" value="<?php echo esc_attr(json_encode($selected_ids)); ?>">
<?php endif; ?>
</form>
</div>
<div class="igny8-modal-footer">
<button type="button" class="igny8-btn igny8-btn-secondary" onclick="igny8CloseImportExportModal()">Cancel</button>
<button type="button" class="igny8-btn igny8-btn-primary" onclick="igny8SubmitExportForm()">
<span class="dashicons dashicons-download"></span> Export <?php echo esc_html($mode_text); ?>
</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,136 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : filters-tpl.php
* @location : /modules/components/filters-tpl.php
* @type : Component
* @scope : Cross-Module
* @allowed : Filter rendering, search functionality
* @reusability : Shared
* @notes : Dynamic filter component for all modules
*/
/*
* <?php igny8_render_filters('planner_clusters'); ?>
* <?php igny8_render_filters('writer_drafts'); ?>
*
* @package Igny8Compact
* @since 1.0.0
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Render filters function
function igny8_render_filters($table_id) {
// Load filters configuration
$filters_config = require plugin_dir_path(__FILE__) . '../config/filters-config.php';
$filters = $filters_config[$table_id] ?? [];
// Load table configuration to get humanize_columns
$tables_config = require plugin_dir_path(__FILE__) . '../config/tables-config.php';
$GLOBALS['igny8_tables_config'] = $tables_config;
$table_config = igny8_get_dynamic_table_config($table_id);
$humanize_columns = $table_config['humanize_columns'] ?? [];
// Set variables for component
$module = explode('_', $table_id)[0];
$tab = explode('_', $table_id)[1] ?? '';
// Debug: Log filters array for verification
// Start output buffering to capture HTML
ob_start();
?>
<!-- Filters HTML -->
<div class="igny8-filters igny8-mb-20" data-table="<?php echo esc_attr($table_id); ?>">
<div class="igny8-filter-bar">
<?php foreach ($filters as $filter_key => $filter_config): ?>
<div class="igny8-filter-group">
<?php if ($filter_config['type'] === 'search'): ?>
<!-- Search Input -->
<input type="text"
class="igny8-search-input igny8-input-md"
id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>"
placeholder="<?php echo esc_attr($filter_config['placeholder'] ?? 'Search...'); ?>"
data-filter="<?php echo esc_attr($filter_key); ?>">
<?php elseif ($filter_config['type'] === 'select'): ?>
<!-- Dropdown Filter -->
<div class="select" data-filter="<?php echo esc_attr($filter_key); ?>">
<button class="select-btn" id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>_btn" data-value="">
<span class="select-text"><?php echo esc_html($filter_config['label'] ?? (in_array($filter_config['field'] ?? $filter_key, $humanize_columns) ? igny8_humanize_label($filter_config['field'] ?? $filter_key) : ucfirst($filter_key))); ?></span>
<span class="dd-arrow">▼</span>
</button>
<div class="select-list" id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>_list">
<div class="select-item" data-value="">All <?php echo esc_html($filter_config['label'] ?? (in_array($filter_config['field'] ?? $filter_key, $humanize_columns) ? igny8_humanize_label($filter_config['field'] ?? $filter_key) : ucfirst($filter_key))); ?></div>
<?php if (isset($filter_config['options'])): ?>
<?php if (is_array($filter_config['options'])): ?>
<?php foreach ($filter_config['options'] as $value => $label): ?>
<div class="select-item" data-value="<?php echo esc_attr($value); ?>"><?php echo esc_html($label); ?></div>
<?php endforeach; ?>
<?php elseif ($filter_config['options'] === 'dynamic_clusters'): ?>
<?php
// Load cluster options dynamically
$cluster_options = igny8_get_cluster_options();
if ($cluster_options) {
foreach ($cluster_options as $option) {
if (!empty($option['value'])) { // Skip "No Cluster" option
echo '<div class="select-item" data-value="' . esc_attr($option['value']) . '">' . esc_html($option['label']) . '</div>';
}
}
}
?>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
<?php elseif ($filter_config['type'] === 'range'): ?>
<!-- Numeric Range Filter -->
<div class="select" data-filter="<?php echo esc_attr($filter_key); ?>">
<button class="select-btn" id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>_btn" data-value="">
<span class="select-text"><?php echo esc_html($filter_config['label'] ?? (in_array($filter_config['field'] ?? $filter_key, $humanize_columns) ? igny8_humanize_label($filter_config['field'] ?? $filter_key) : ucfirst($filter_key))); ?></span>
<span>▼</span>
</button>
<div class="select-list igny8-dropdown-panel" id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>_list">
<div style="margin-bottom: 10px;">
<label class="igny8-text-sm igny8-mb-5 igny8-text-muted">Min <?php echo esc_html($filter_config['label'] ?? (in_array($filter_config['field'] ?? $filter_key, $humanize_columns) ? igny8_humanize_label($filter_config['field'] ?? $filter_key) : ucfirst($filter_key))); ?>:</label>
<input type="number" id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>_min" placeholder="0" class="igny8-input-sm">
</div>
<div style="margin-bottom: 10px;">
<label class="igny8-text-sm igny8-mb-5 igny8-text-muted">Max <?php echo esc_html($filter_config['label'] ?? (in_array($filter_config['field'] ?? $filter_key, $humanize_columns) ? igny8_humanize_label($filter_config['field'] ?? $filter_key) : ucfirst($filter_key))); ?>:</label>
<input type="number" id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>_max" placeholder="10000" class="igny8-input-sm">
</div>
<div class="igny8-flex igny8-flex-gap-10">
<button type="button" id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>_ok" class="igny8-btn igny8-btn-primary igny8-flex igny8-p-5 igny8-text-xs">Ok</button>
<button type="button" id="<?php echo esc_attr($table_id); ?>_filter_<?php echo esc_attr($filter_key); ?>_clear" class="igny8-btn igny8-btn-secondary igny8-flex igny8-p-5 igny8-text-xs">Clear</button>
</div>
</div>
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
<div class="igny8-filter-actions">
<button class="igny8-btn igny8-btn-primary" id="<?php echo esc_attr($table_id); ?>_filter_apply_btn">Apply Filters</button>
<button class="igny8-btn igny8-btn-secondary" id="<?php echo esc_attr($table_id); ?>_filter_reset_btn">Reset</button>
</div>
</div>
</div>
<?php
return ob_get_clean();
}
// Set default values
$table_id = $table_id ?? 'data_table';
$filters = $filters ?? [];
$module = $module ?? '';
$tab = $tab ?? '';
// Debug state: Filter HTML rendered
if (function_exists('igny8_debug_state')) {
igny8_debug_state('FILTER_HTML_RENDERED', true, 'Filter HTML rendered for ' . $table_id);
}
?>

View File

@@ -0,0 +1,176 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : forms-tpl.php
* @location : /modules/components/forms-tpl.php
* @type : Component
* @scope : Cross-Module
* @allowed : Form rendering, validation, data processing
* @reusability : Shared
* @notes : Dynamic form component for all modules
*/
if (!defined('ABSPATH')) {
exit;
}
function igny8_render_inline_form_row($table_id, $mode = 'add', $record_data = []) {
$form_config = igny8_get_form_config($table_id);
if (!$form_config) {
return '<tr><td colspan="100%">Form not configured for table: ' . esc_html($table_id) . '</td></tr>';
}
$row_id_attr = ($mode === 'edit' && !empty($record_data['id']))
? ' data-id="' . esc_attr($record_data['id']) . '"'
: '';
ob_start(); ?>
<tr class="igny8-inline-form-row" data-mode="<?php echo esc_attr($mode); ?>"<?php echo $row_id_attr; ?>>
<td><input type="checkbox" disabled></td>
<?php foreach ($form_config['fields'] as $field): ?>
<td>
<?php echo igny8_render_form_field($field, $record_data, $mode); ?>
</td>
<?php endforeach; ?>
<td class="igny8-align-center igny8-actions">
<button
type="button"
class="igny8-btn igny8-btn-success igny8-btn-sm igny8-form-save"
data-table-id="<?php echo esc_attr($table_id); ?>"
data-nonce="<?php echo esc_attr(wp_create_nonce('igny8_ajax_nonce')); ?>"
title="Save"
>
<svg width="20" height="20" viewBox="0 0 26 26" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
</svg>
</button>
<button
type="button"
class="igny8-btn igny8-btn-secondary igny8-btn-sm igny8-form-cancel"
title="Cancel"
>
<svg width="20" height="20" viewBox="0 0 26 26" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="5" y1="5" x2="20" y2="20"></line>
<line x1="5" y1="20" x2="20" y2="5"></line>
</svg>
</button>
</td>
</tr>
<style>
tr.igny8-inline-form-row {
animation: slideInForm 0.3s ease-out;
}
@keyframes slideInForm {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
tr.igny8-inline-form-row td {
transition: all 0.2s ease;
}
tr.igny8-inline-form-row:hover td {
background-color: rgba(0, 123, 255, 0.05);
}
</style>
<?php
return ob_get_clean();
}
function igny8_render_form_field($field, $record_data = [], $mode = 'add') {
$field_name = $field['name'];
$field_type = $field['type'];
$field_label = $field['label'] ?? ucfirst($field_name);
$field_value = isset($record_data[$field_name]) ? $record_data[$field_name] : ($field['default'] ?? '');
$is_required = !empty($field['required']);
switch ($field_type) {
case 'number':
return igny8_render_number_field($field_name, $field_label, $field_value, $is_required, $field['step'] ?? '');
case 'select':
return igny8_render_select_field($field_name, $field_label, $field_value, $field, $is_required);
case 'textarea':
return igny8_render_textarea_field($field_name, $field_label, $field_value, $is_required);
default:
return igny8_render_text_field($field_name, $field_label, $field_value, $is_required);
}
}
function igny8_render_text_field($name, $label, $value, $required = false) {
ob_start();
?>
<input type="text" name="<?php echo esc_attr($name); ?>" placeholder="<?php echo esc_attr($label); ?>"
class="igny8-input-sm" value="<?php echo esc_attr($value); ?>"<?php echo $required ? ' required' : ''; ?> />
<?php
return ob_get_clean();
}
function igny8_render_number_field($name, $label, $value, $required = false, $step = '') {
ob_start();
?>
<input type="number" name="<?php echo esc_attr($name); ?>" placeholder="<?php echo esc_attr($label); ?>"
class="igny8-input-sm" value="<?php echo esc_attr($value); ?>"<?php
echo $step ? ' step="' . esc_attr($step) . '"' : '';
echo $required ? ' required' : ''; ?> />
<?php
return ob_get_clean();
}
function igny8_render_select_field($name, $label, $value, $field_config, $required = false) {
$display_text = 'Select ' . esc_html($label);
$options = [];
// Get options
if (isset($field_config['source']) && function_exists($field_config['source'])) {
try {
$dynamic_options = call_user_func($field_config['source']);
foreach ($dynamic_options as $option) {
$val = $option['value'] ?? $option;
$lbl = $option['label'] ?? $option;
$options[$val] = $lbl;
if ($value == $val) $display_text = esc_html($lbl);
}
} catch (Exception $e) {
$options = [];
}
} else {
$options = $field_config['options'] ?? [];
foreach ($options as $val => $lbl) {
if ($value == $val) $display_text = esc_html($lbl);
}
}
ob_start();
?>
<div class="select">
<button type="button" class="select-btn" name="<?php echo esc_attr($name); ?>" data-value="<?php echo esc_attr($value); ?>">
<span class="select-text"><?php echo $display_text; ?></span>
<span>▼</span>
</button>
<div class="select-list" style="max-height:200px;overflow-y:auto;">
<div class="select-item" data-value="">Select <?php echo esc_html($label); ?></div>
<?php foreach ($options as $val => $lbl): ?>
<div class="select-item" data-value="<?php echo esc_attr($val); ?>"><?php echo esc_html($lbl); ?></div>
<?php endforeach; ?>
</div>
</div>
<?php
return ob_get_clean();
}
function igny8_render_textarea_field($name, $label, $value, $required = false) {
ob_start();
?>
<textarea name="<?php echo esc_attr($name); ?>" placeholder="<?php echo esc_attr($label); ?>"
class="igny8-input-sm" rows="3"<?php echo $required ? ' required' : ''; ?>><?php echo esc_textarea($value); ?></textarea>
<?php
return ob_get_clean();
}
?>

View File

@@ -0,0 +1,67 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : import-modal-tpl.php
* @location : /modules/components/import-modal-tpl.php
* @type : Component
* @scope : Cross-Module
* @allowed : Modal rendering, import functionality
* @reusability : Shared
* @notes : Dynamic import modal component for all modules
*/
// Prevent direct access
if (!defined('ABSPATH') && !defined('IGNY8_INCLUDE_TEMPLATE')) {
exit;
}
// Get configuration for this table
$config = igny8_get_import_export_config($table_id);
if (!$config) {
return;
}
$singular = $config['singular'];
$plural = $config['plural'];
?>
<div id="igny8-import-export-modal" class="igny8-modal" data-table-id="<?php echo esc_attr($table_id); ?>">
<div class="igny8-modal-content">
<div class="igny8-modal-header">
<h3>Import <?php echo esc_html($plural); ?></h3>
<button class="igny8-btn-close" onclick="igny8CloseImportExportModal()">&times;</button>
</div>
<div class="igny8-modal-body">
<p>Import <?php echo esc_html(strtolower($plural)); ?> from a CSV file. Use the template below for proper format.</p>
<!-- Template Download -->
<div class="igny8-mb-15">
<button type="button" class="igny8-btn igny8-btn-outline" onclick="igny8DownloadTemplate('<?php echo esc_attr($table_id); ?>')">
<span class="dashicons dashicons-download"></span> Download <?php echo esc_html($singular); ?> Template
</button>
</div>
<!-- Import Form -->
<form id="igny8-modal-import-form" enctype="multipart/form-data">
<input type="hidden" name="action" value="igny8_run_import">
<input type="hidden" name="nonce" value="">
<input type="hidden" name="import_type" value="<?php echo esc_attr($config['type']); ?>">
<div class="igny8-form-group">
<label for="import-file">Select CSV File</label>
<input type="file" id="import-file" name="import_file" accept=".csv" required>
<p class="description">Upload a CSV file with your <?php echo esc_html(strtolower($plural)); ?>. Use the template above for proper format.</p>
</div>
</form>
</div>
<div class="igny8-modal-footer">
<button type="button" class="igny8-btn igny8-btn-secondary" onclick="igny8CloseImportExportModal()">Cancel</button>
<button type="button" class="igny8-btn igny8-btn-success" onclick="igny8SubmitImportForm()">
<span class="dashicons dashicons-upload"></span> Import <?php echo esc_html($plural); ?>
</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,140 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : kpi-tpl.php
* @location : /modules/components/kpi-tpl.php
* @type : Component
* @scope : Cross-Module
* @allowed : KPI rendering, metrics display
* @reusability : Shared
* @notes : Dynamic KPI component for all modules
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Get KPI data for a specific submodule
*
* @param string $table_id The table ID (e.g., 'planner_keywords')
* @param array $kpi_config The KPI configuration array
* @return array KPI data array
*/
function igny8_get_kpi_data($table_id, $kpi_config) {
global $wpdb;
$kpi_data = [];
// Get table name from table_id
$table_name = igny8_get_table_name($table_id);
if (empty($table_name) || empty($kpi_config)) {
return $kpi_data;
}
// Execute each KPI query
foreach ($kpi_config as $kpi_key => $kpi_info) {
if (!isset($kpi_info['query'])) {
continue;
}
// Replace placeholders with actual values
$query = str_replace('{table_name}', $table_name, $kpi_info['query']);
$query = str_replace('{prefix}', $wpdb->prefix, $query);
// Execute query safely
$result = $wpdb->get_var($query);
// Store result (handle null/empty results)
$kpi_data[$kpi_key] = $result !== null ? (int) $result : 0;
}
return $kpi_data;
}
/**
* Get actual table name from table ID
*
* @param string $table_id The table ID (e.g., 'planner_keywords')
* @return string The actual table name (e.g., 'wp_igny8_keywords')
*/
function igny8_get_table_name($table_id) {
global $wpdb;
// Map table IDs to actual table names
$table_mapping = [
'planner_home' => $wpdb->prefix . 'igny8_keywords', // Uses keywords table as base for home metrics
'planner_keywords' => $wpdb->prefix . 'igny8_keywords',
'planner_clusters' => $wpdb->prefix . 'igny8_clusters',
'planner_ideas' => $wpdb->prefix . 'igny8_content_ideas',
'writer_home' => $wpdb->prefix . 'igny8_content_ideas', // Uses ideas table as base for home metrics
'writer_drafts' => $wpdb->prefix . 'igny8_tasks',
'writer_published' => $wpdb->prefix . 'igny8_tasks',
'writer_templates' => $wpdb->prefix . 'igny8_prompts',
'writer_tasks' => $wpdb->prefix . 'igny8_tasks',
'optimizer_audits' => $wpdb->prefix . 'igny8_logs',
'optimizer_suggestions' => $wpdb->prefix . 'igny8_suggestions',
'linker_backlinks' => $wpdb->prefix . 'igny8_backlinks',
'linker_campaigns' => $wpdb->prefix . 'igny8_campaigns',
'linker_links' => $wpdb->prefix . 'igny8_links',
'personalize_rewrites' => $wpdb->prefix . 'igny8_variations',
'personalize_tones' => $wpdb->prefix . 'igny8_sites',
'personalize_data' => $wpdb->prefix . 'igny8_personalization',
'personalize_variations' => $wpdb->prefix . 'igny8_variations',
'personalize_records' => $wpdb->prefix . 'igny8_personalization',
'thinker_prompts' => '' // No table needed for prompts submodule
];
$table_name = $table_mapping[$table_id] ?? '';
// Throw error if unknown table ID (except for special cases that don't need tables)
if (empty($table_name) && !in_array($table_id, ['thinker_prompts'])) {
throw new InvalidArgumentException("Unknown table ID: {$table_id}");
}
return $table_name;
}
/**
* Check if a table exists in the database
*
* @param string $table_name The table name to check
* @return bool True if table exists, false otherwise
*/
function igny8_table_exists($table_name) {
global $wpdb;
$result = $wpdb->get_var($wpdb->prepare(
"SHOW TABLES LIKE %s",
$table_name
));
return $result === $table_name;
}
/**
* Get KPI data with fallback for missing tables
*
* @param string $table_id The table ID
* @param array $kpi_config The KPI configuration
* @return array KPI data array with fallback values
*/
function igny8_get_kpi_data_safe($table_id, $kpi_config) {
$table_name = igny8_get_table_name($table_id);
// If table doesn't exist, return empty data
if (!igny8_table_exists($table_name)) {
$fallback_data = [];
foreach ($kpi_config as $kpi_key => $kpi_info) {
$fallback_data[$kpi_key] = 0;
}
return $fallback_data;
}
// Get real data
return igny8_get_kpi_data($table_id, $kpi_config);
}

View File

@@ -0,0 +1,136 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : pagination-tpl.php
* @location : /modules/components/pagination-tpl.php
* @type : Component
* @scope : Cross-Module
* @allowed : Pagination rendering, navigation controls
* @reusability : Shared
* @notes : Dynamic pagination component for all modules
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Render pagination function
function igny8_render_pagination($table_id, $pagination = []) {
// Set variables for component
$module = explode('_', $table_id)[0];
$tab = explode('_', $table_id)[1] ?? '';
// Set default pagination values
$pagination = array_merge([
'current_page' => 1,
'total_pages' => 1,
'per_page' => 10,
'total_items' => 0
], $pagination);
// Start output buffering to capture HTML
ob_start();
?>
<!-- Pagination -->
<div class="igny8-pagination igny8-mt-20"
data-table="<?php echo esc_attr($table_id); ?>"
data-current-page="<?php echo esc_attr($pagination['current_page']); ?>"
data-per-page="<?php echo esc_attr($pagination['per_page']); ?>"
data-total-items="<?php echo esc_attr($pagination['total_items']); ?>"
data-module="<?php echo esc_attr($module); ?>"
data-tab="<?php echo esc_attr($tab); ?>">
<button class="igny8-btn igny8-btn-sm igny8-btn-outline igny8-page-btn"
data-page="<?php echo max(1, $pagination['current_page'] - 1); ?>"
<?php echo $pagination['current_page'] <= 1 ? 'disabled' : ''; ?>>
Previous
</button>
<?php if ($pagination['total_items'] > 20): ?>
<?php
$current = $pagination['current_page'];
$total = $pagination['total_pages'];
?>
<?php // Always show first 2 pages ?>
<?php for ($i = 1; $i <= min(2, $total); $i++): ?>
<button class="igny8-btn igny8-btn-sm igny8-page-btn <?php echo $i === $current ? 'igny8-btn-primary' : 'igny8-btn-outline'; ?>"
data-page="<?php echo $i; ?>">
<?php echo $i; ?>
</button>
<?php endfor; ?>
<?php // Add ellipsis if there's a gap ?>
<?php if ($total > 4 && $current > 3): ?>
<span style="margin: 0 8px; color: #666;">...</span>
<?php endif; ?>
<?php // Show current page if it's not in first 2 or last 2 ?>
<?php if ($current > 2 && $current < $total - 1): ?>
<button class="igny8-btn igny8-btn-sm igny8-page-btn igny8-btn-primary"
data-page="<?php echo $current; ?>">
<?php echo $current; ?>
</button>
<?php endif; ?>
<?php // Add ellipsis before last 2 if there's a gap ?>
<?php if ($total > 4 && $current < $total - 2): ?>
<span style="margin: 0 8px; color: #666;">...</span>
<?php endif; ?>
<?php // Always show last 2 pages (if different from first 2) ?>
<?php for ($i = max(3, $total - 1); $i <= $total; $i++): ?>
<?php if ($i > 2): // Don't duplicate first 2 pages ?>
<button class="igny8-btn igny8-btn-sm igny8-page-btn <?php echo $i === $current ? 'igny8-btn-primary' : 'igny8-btn-outline'; ?>"
data-page="<?php echo $i; ?>">
<?php echo $i; ?>
</button>
<?php endif; ?>
<?php endfor; ?>
<?php endif; ?>
<button class="igny8-btn igny8-btn-sm igny8-btn-outline igny8-page-btn"
data-page="<?php echo min($pagination['total_pages'], $pagination['current_page'] + 1); ?>"
<?php echo $pagination['current_page'] >= $pagination['total_pages'] ? 'disabled' : ''; ?>>
Next
</button>
<!-- Records per page selector -->
<div class="igny8-per-page-selector" style="margin-left: 20px; display: inline-flex; align-items: center; gap: 8px;">
<label for="<?php echo esc_attr($table_id); ?>_per_page" style="font-size: 12px; color: #666;">Show:</label>
<select id="<?php echo esc_attr($table_id); ?>_per_page" class="igny8-per-page-select" data-table="<?php echo esc_attr($table_id); ?>" style="padding: 4px 8px; font-size: 12px; border: 1px solid #ddd; border-radius: 4px;">
<option value="10" <?php selected($pagination['per_page'], 10); ?>>10</option>
<option value="20" <?php selected($pagination['per_page'], 20); ?>>20</option>
<option value="50" <?php selected($pagination['per_page'], 50); ?>>50</option>
<option value="100" <?php selected($pagination['per_page'], 100); ?>>100</option>
</select>
<span style="font-size: 12px; color: #666;">per page</span>
</div>
<span style="margin-left: 12px; font-size: 12px; color: #666;">
Showing <?php echo (($pagination['current_page'] - 1) * $pagination['per_page']) + 1; ?>-<?php echo min($pagination['current_page'] * $pagination['per_page'], $pagination['total_items']); ?> of <?php echo $pagination['total_items']; ?> items
</span>
</div>
<?php
return ob_get_clean();
}
// Set default values
$table_id = $table_id ?? 'data_table';
$pagination = $pagination ?? [
'current_page' => 1,
'total_pages' => 1,
'per_page' => 10,
'total_items' => 0
];
$module = $module ?? '';
$tab = $tab ?? '';
// Debug state: Pagination HTML rendered
if (function_exists('igny8_debug_state')) {
igny8_debug_state('PAGINATION_HTML_RENDERED', true, 'Pagination HTML rendered for ' . $table_id);
}
?>

View File

@@ -0,0 +1,870 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : table-tpl.php
* @location : /modules/components/table-tpl.php
* @type : Component
* @scope : Cross-Module
* @allowed : Table rendering, data formatting, display functions
* @reusability : Shared
* @notes : Dynamic table component with formatting functions for all modules
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Check if a column should have proper case transformation applied
*
* @param string $column_name The column name
* @param array $column_config The column configuration
* @return bool Whether to apply proper case transformation
*/
function igny8_should_apply_proper_case($column_name, $column_config) {
// Only apply to enum fields
$column_type = $column_config['type'] ?? 'text';
return $column_type === 'enum';
}
/**
* Format enum field values with proper case display
*
* @param string $value The database value
* @param string $field_name The field name
* @return string The formatted display value
*/
function igny8_format_enum_field($value, $field_name) {
if (empty($value)) {
return $value;
}
// Handle specific field mappings for consistent display
$field_mappings = [
'status' => [
'queued' => 'Queued',
'in_progress' => 'In Progress',
'draft' => 'Draft',
'review' => 'Review',
'published' => 'Published'
],
'priority' => [
'low' => 'Low',
'medium' => 'Medium',
'high' => 'High',
'urgent' => 'Urgent'
],
'content_type' => [
'blog_post' => 'Blog Post',
'landing_page' => 'Landing Page',
'product_page' => 'Product Page',
'guide_tutorial' => 'Guide Tutorial',
'news_article' => 'News Article',
'review' => 'Review',
'comparison' => 'Comparison',
'email' => 'Email',
'social_media' => 'Social Media'
]
];
// Check for specific field mapping first
if (isset($field_mappings[$field_name][$value])) {
return $field_mappings[$field_name][$value];
}
// Fallback: convert snake_case to Title Case
return ucwords(str_replace('_', ' ', $value));
}
/**
* Get proper case for enum values
*
* @param string $value The snake_case value
* @return string The proper case value
*/
function igny8_get_proper_case_enum($value) {
$enum_mapping = [
// Content Type values
'blog_post' => 'Blog Post',
'landing_page' => 'Landing Page',
'product_page' => 'Product Page',
'guide_tutorial' => 'Guide Tutorial',
'news_article' => 'News Article',
'review' => 'Review',
'comparison' => 'Comparison',
'page' => 'Page',
'product' => 'Product',
'product_description' => 'Product Description',
'email' => 'Email',
'social_media' => 'Social Media',
// Status values
'unmapped' => 'Unmapped',
'mapped' => 'Mapped',
'queued' => 'Queued',
'published' => 'Published',
'active' => 'Active',
'inactive' => 'Inactive',
'archived' => 'Archived',
'draft' => 'Draft',
'in_progress' => 'In Progress',
'completed' => 'Completed',
'cancelled' => 'Cancelled',
'pending' => 'Pending',
'failed' => 'Failed',
'lost' => 'Lost',
'approved' => 'Approved',
'rejected' => 'Rejected',
'needs_revision' => 'Needs Revision',
'planning' => 'Planning',
'paused' => 'Paused',
'review' => 'Review',
// Intent values
'informational' => 'Informational',
'navigational' => 'Navigational',
'transactional' => 'Transactional',
'commercial' => 'Commercial',
// Link type values
'dofollow' => 'Dofollow',
'nofollow' => 'Nofollow',
'sponsored' => 'Sponsored',
'ugc' => 'UGC',
// Coverage status values
'fully_mapped' => 'Fully Mapped',
'partially_mapped' => 'Partially Mapped',
'not_mapped' => 'Not Mapped',
// Suggestion type values
'title_optimization' => 'Title Optimization',
'meta_description' => 'Meta Description',
'heading_structure' => 'Heading Structure',
'content_improvement' => 'Content Improvement',
'internal_linking' => 'Internal Linking',
// Tone values
'professional' => 'Professional',
'casual' => 'Casual',
'friendly' => 'Friendly',
'authoritative' => 'Authoritative',
'conversational' => 'Conversational',
// Category values
'business' => 'Business',
'creative' => 'Creative',
'technical' => 'Technical',
'marketing' => 'Marketing',
'educational' => 'Educational'
];
return $enum_mapping[$value] ?? ucwords(str_replace('_', ' ', $value));
}
/**
* Apply proper case transformation to a value
*
* @param string $value The value to transform
* @param string $column_name The column name for special handling
* @return string The transformed value
*/
function igny8_apply_proper_case($value, $column_name) {
// Apply global proper case transformation to all enum fields
return igny8_get_proper_case_enum($value);
}
/**
* Format date for created_at column (tasks table)
* Shows hours/days ago if less than 30 days, month/day if greater
*/
function igny8_format_created_date($date_string) {
if (empty($date_string)) {
return 'Never';
}
try {
// Use WordPress timezone
$wp_timezone = wp_timezone();
$date = new DateTime($date_string, $wp_timezone);
$now = new DateTime('now', $wp_timezone);
$diff = $now->diff($date);
// Calculate total days difference
$total_days = $diff->days;
// If less than 30 days, show relative time
if ($total_days < 30) {
if ($total_days == 0) {
if ($diff->h > 0) {
$result = $diff->h . ' hour' . ($diff->h > 1 ? 's' : '') . ' ago';
} elseif ($diff->i > 0) {
$result = $diff->i . ' minute' . ($diff->i > 1 ? 's' : '') . ' ago';
} else {
$result = 'Just now';
}
} else {
$result = $total_days . ' day' . ($total_days > 1 ? 's' : '') . ' ago';
}
} else {
// If 30+ days, show month and day
$result = $date->format('M j');
}
return $result;
} catch (Exception $e) {
// Fallback to original date if parsing fails
$fallback = wp_date('M j', strtotime($date_string));
return $fallback;
}
}
/**
* Format date for updated_at column (drafts/published tables)
* Shows hours/days ago if less than 30 days, month/day if greater
*/
function igny8_format_updated_date($date_string) {
if (empty($date_string)) {
return 'Never';
}
try {
// Use WordPress timezone
$wp_timezone = wp_timezone();
$date = new DateTime($date_string, $wp_timezone);
$now = new DateTime('now', $wp_timezone);
$diff = $now->diff($date);
// Calculate total days difference
$total_days = $diff->days;
// If less than 30 days, show relative time
if ($total_days < 30) {
if ($total_days == 0) {
if ($diff->h > 0) {
return $diff->h . ' hour' . ($diff->h > 1 ? 's' : '') . ' ago';
} elseif ($diff->i > 0) {
return $diff->i . ' minute' . ($diff->i > 1 ? 's' : '') . ' ago';
} else {
return 'Just now';
}
} else {
return $total_days . ' day' . ($total_days > 1 ? 's' : '') . ' ago';
}
} else {
// If 30+ days, show month and day
return $date->format('M j');
}
} catch (Exception $e) {
// Fallback to original date if parsing fails
return wp_date('M j', strtotime($date_string));
}
}
/**
* Display image prompts in a formatted way for table display
*
* @param string $image_prompts JSON string of image prompts
* @return string Formatted display string
*/
function igny8_display_image_prompts($image_prompts) {
if (empty($image_prompts)) {
return '<span class="text-muted">No prompts</span>';
}
// Try to decode JSON
$prompts = json_decode($image_prompts, true);
if (!$prompts || !is_array($prompts)) {
return '<span class="text-muted">Invalid format</span>';
}
$output = '<div class="image-prompts-display">';
$count = 0;
foreach ($prompts as $key => $prompt) {
if (!empty($prompt)) {
$count++;
$label = ucfirst(str_replace('_', ' ', $key));
$truncated = strlen($prompt) > 50 ? substr($prompt, 0, 50) . '...' : $prompt;
$output .= '<div class="prompt-item mb-1">';
$output .= '<strong>' . esc_html($label) . ':</strong> ';
$output .= '<span title="' . esc_attr($prompt) . '">' . esc_html($truncated) . '</span>';
$output .= '</div>';
}
}
if ($count === 0) {
$output = '<span class="text-muted">No prompts</span>';
} else {
$output .= '</div>';
}
return $output;
}
/**
* Format structured description for display
*/
function igny8_format_structured_description_for_display($structured_description) {
if (!is_array($structured_description)) {
return 'No structured outline available';
}
$formatted = "<div class='igny8-structured-description'>";
$formatted .= "<h4>Content Outline</h4>";
// Handle introduction section with hook
if (!empty($structured_description['introduction'])) {
$formatted .= "<div class='description-intro'>";
// Add hook if it exists
if (!empty($structured_description['introduction']['hook'])) {
$formatted .= "<div class='intro-hook'><strong>Hook:</strong> " . esc_html($structured_description['introduction']['hook']) . "</div>";
}
// Add paragraphs if they exist
if (!empty($structured_description['introduction']['paragraphs']) && is_array($structured_description['introduction']['paragraphs'])) {
$paragraph_count = 1;
foreach ($structured_description['introduction']['paragraphs'] as $paragraph) {
if (!empty($paragraph['details'])) {
$formatted .= "<div class='intro-paragraph'><strong>Intro Paragraph " . $paragraph_count . ":</strong> " . esc_html($paragraph['details']) . "</div>";
$paragraph_count++;
}
}
}
$formatted .= "</div>";
}
// Handle H2 sections if they exist
if (!empty($structured_description['H2']) && is_array($structured_description['H2'])) {
foreach ($structured_description['H2'] as $h2_section) {
$formatted .= "<div class='igny8-h2-section'>";
$formatted .= "<h5>" . esc_html($h2_section['heading']) . "</h5>";
if (!empty($h2_section['subsections'])) {
$formatted .= "<ul class='igny8-subsections'>";
foreach ($h2_section['subsections'] as $h3_section) {
$formatted .= "<li>";
$formatted .= "<strong>" . esc_html($h3_section['subheading']) . "</strong>";
$formatted .= " <span class='igny8-content-type'>(" . esc_html($h3_section['content_type']) . ")</span>";
$formatted .= "<br><small>" . esc_html($h3_section['details']) . "</small>";
$formatted .= "</li>";
}
$formatted .= "</ul>";
}
$formatted .= "</div>";
}
}
$formatted .= "</div>";
return $formatted;
}
/**
* Fetch real table data from database
* Phase-2: Real Data Loading from Config
*
* @param string $tableId The table identifier
* @param array $filters Optional filters to apply
* @param int $page Page number for pagination
* @return array Real data structure
*/
function igny8_fetch_table_data($tableId, $filters = [], $page = 1, $per_page = null) {
global $wpdb;
// Sanitize all inputs
$tableId = sanitize_text_field($tableId);
$page = intval($page);
$per_page = $per_page ? intval($per_page) : get_option('igny8_records_per_page', 20);
// Sanitize filters array
if (is_array($filters)) {
$filters = array_map('sanitize_text_field', $filters);
} else {
$filters = [];
}
// Get table configuration to apply default filters
$tables_config = require plugin_dir_path(__FILE__) . '../config/tables-config.php';
$GLOBALS['igny8_tables_config'] = $tables_config;
$table_config = igny8_get_dynamic_table_config($tableId);
// Apply default filters - merge with existing filters
if (isset($table_config['default_filter'])) {
$default_filters = $table_config['default_filter'];
foreach ($default_filters as $key => $value) {
// Only apply default filter if not already set by user
if (!isset($filters[$key]) || empty($filters[$key])) {
$filters[$key] = $value;
}
}
}
// Force completed status filter for writer_published table
if ($tableId === 'writer_published') {
$filters['status'] = ['completed'];
}
// Get table name from table ID
$table_name = igny8_get_table_name($tableId);
// Check if table exists
if (!igny8_table_exists($table_name)) {
// Return empty data if table doesn't exist
return [
'rows' => [],
'pagination' => [
'page' => $page,
'total' => 0,
'per_page' => $per_page,
'total_pages' => 0
]
];
}
// Build WHERE clause for filters
$where_conditions = [];
$where_values = [];
if (!empty($filters)) {
foreach ($filters as $key => $value) {
if (!empty($value)) {
// Check if this is a range filter (min/max)
if (strpos($key, '-min') !== false) {
$field_name = str_replace('-min', '', $key);
$where_conditions[] = "`{$field_name}` >= %d";
$where_values[] = intval($value);
} elseif (strpos($key, '-max') !== false) {
$field_name = str_replace('-max', '', $key);
$where_conditions[] = "`{$field_name}` <= %d";
$where_values[] = intval($value);
} elseif (in_array($key, ['status', 'intent'])) {
// For dropdown filters (status, intent), handle both single values and arrays
if (is_array($value)) {
// For array values (like ['draft'] from default filters)
$placeholders = implode(',', array_fill(0, count($value), '%s'));
$where_conditions[] = "`{$key}` IN ({$placeholders})";
$where_values = array_merge($where_values, $value);
} else {
// For single values
$where_conditions[] = "`{$key}` = %s";
$where_values[] = $value;
}
} elseif ($key === 'difficulty') {
// For difficulty, convert text label to numeric range
$difficulty_range = igny8_get_difficulty_numeric_range($value);
if ($difficulty_range) {
$where_conditions[] = "`{$key}` >= %d AND `{$key}` <= %d";
$where_values[] = $difficulty_range['min'];
$where_values[] = $difficulty_range['max'];
}
} else {
// For keyword search, use LIKE
$where_conditions[] = "`{$key}` LIKE %s";
$where_values[] = '%' . $wpdb->esc_like($value) . '%';
}
}
}
}
$where_clause = '';
if (!empty($where_conditions)) {
$where_clause = 'WHERE ' . implode(' AND ', $where_conditions);
}
// Build JOIN queries from column configurations
$join_queries = [];
foreach ($config_columns as $col_key => $col_config) {
if (isset($col_config['join_query'])) {
$join_query = str_replace(['{prefix}', '{table_name}'], [$wpdb->prefix, $table_name], $col_config['join_query']);
if (!in_array($join_query, $join_queries)) {
$join_queries[] = $join_query;
}
}
}
// Get total count for pagination (with JOINs)
$join_clause = '';
if (!empty($join_queries)) {
$join_clause = implode(' ', $join_queries);
}
$count_query = "SELECT COUNT(*) FROM `{$table_name}` {$join_clause} {$where_clause}";
if (!empty($where_values)) {
$total_count = $wpdb->get_var($wpdb->prepare($count_query, $where_values));
} else {
$total_count = $wpdb->get_var($count_query);
}
// Calculate pagination
$total_pages = ceil($total_count / $per_page);
$offset = ($page - 1) * $per_page;
// Build main query with JOINs and proper field selection
$select_fields = [];
// Add base table fields
$select_fields[] = "{$table_name}.*";
// Process column configurations to build select fields
foreach ($config_columns as $col_key => $col_config) {
if (isset($col_config['select_field'])) {
$select_fields[] = $col_config['select_field'];
}
}
// Build final query with JOINs
$select_clause = implode(', ', $select_fields);
$query = "SELECT {$select_clause} FROM `{$table_name}` {$join_clause} {$where_clause} ORDER BY {$table_name}.id DESC LIMIT %d OFFSET %d";
$query_values = array_merge($where_values, [$per_page, $offset]);
// Execute query
if (!empty($where_values)) {
$final_query = $wpdb->prepare($query, $query_values);
$results = $wpdb->get_results($final_query, ARRAY_A);
} else {
$final_query = $wpdb->prepare($query, $per_page, $offset);
$results = $wpdb->get_results($final_query, ARRAY_A);
}
// Format results for frontend - return complete table body HTML using PHP templating
$table_body_html = '';
// Load table configuration once (needed for both results and empty state)
$tables_config = require plugin_dir_path(__FILE__) . '../config/tables-config.php';
$GLOBALS['igny8_tables_config'] = $tables_config;
$table_config = igny8_get_dynamic_table_config($tableId);
$config_columns = $table_config['columns'] ?? [];
// Get column keys from humanize_columns, including date columns
$column_keys = $table_config['humanize_columns'] ?? array_keys($config_columns);
if ($results) {
foreach ($results as $row) {
$id = $row['id'] ?? 0;
// Use PHP templating instead of string concatenation
ob_start();
?>
<tr data-id="<?php echo esc_attr($id); ?>">
<td><input type="checkbox" class="igny8-checkbox" value="<?php echo esc_attr($id); ?>"></td>
<?php
// If no config found, fallback to all available columns
if (empty($column_keys)) {
$column_keys = array_keys($row);
}
foreach ($column_keys as $col) {
// Check if this is a calculated field
$column_config = $config_columns[$col] ?? [];
if (isset($column_config['calculated']) && $column_config['calculated'] === true) {
// Execute calculation query for this row
$calculation_query = $column_config['calculation_query'] ?? '';
if (!empty($calculation_query)) {
// Replace placeholders in the query
$query = str_replace(['{prefix}', '{table_name}'], [$wpdb->prefix, $table_name], $calculation_query);
$query = str_replace('{table_name}.id', $row['id'], $query);
$value = $wpdb->get_var($query) ?? 0;
} else {
$value = 0;
}
} elseif ($col === 'cluster_id' || $col === 'keyword_cluster_id') {
// Check if this column has a display_field from JOIN query
$display_field = $column_config['display_field'] ?? null;
if ($display_field && isset($row[$display_field])) {
$value = $row[$display_field];
} else {
// Fallback to fetching cluster name by ID
$cluster_id = isset($row[$col]) ? $row[$col] : '';
$value = igny8_get_cluster_term_name($cluster_id);
}
} elseif ($col === 'source') {
// Handle source column - use actual field value
$value = isset($row[$col]) ? $row[$col] : '';
} elseif ($col === 'sector_id') {
$sector_id = isset($row[$col]) ? $row[$col] : '';
$value = igny8_get_sector_name($sector_id);
} elseif ($col === 'difficulty' || $col === 'avg_difficulty') {
$difficulty = isset($row[$col]) ? $row[$col] : '';
$value = igny8_get_difficulty_range_name($difficulty);
} elseif (in_array($col, ['created_at', 'created_date', 'last_audit', 'discovered_date', 'start_date'])) {
// Format created/audit/discovered/start date columns for all tables
$date_value = isset($row[$col]) ? $row[$col] : '';
$value = igny8_format_created_date($date_value);
} elseif (in_array($col, ['updated_at', 'updated_date', 'next_audit', 'end_date'])) {
// Format updated/next audit/end date columns for all tables
$date_value = isset($row[$col]) ? $row[$col] : '';
$value = igny8_format_updated_date($date_value);
} else {
$value = isset($row[$col]) ? $row[$col] : '';
// Apply proper case transformation to eligible columns
if (!empty($value) && igny8_should_apply_proper_case($col, $column_config)) {
$value = igny8_apply_proper_case($value, $col);
}
}
// Special handling for idea_title column in planner_ideas table
if ($col === 'idea_title' && $tableId === 'planner_ideas') {
$description = isset($row['idea_description']) ? $row['idea_description'] : '';
$image_prompts = isset($row['image_prompts']) ? $row['image_prompts'] : '';
// Format description for display (handle JSON vs plain text)
$display_description = '';
if (!empty($description)) {
$decoded = json_decode($description, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
// Format structured description for display
$display_description = igny8_format_structured_description_for_display($decoded);
} else {
// Use as plain text
$display_description = $description;
}
}
?>
<td>
<div class="igny8-title-with-badge">
<span class="igny8-title-text"><?php echo esc_html($value); ?></span>
<div class="igny8-title-actions">
<?php if (!empty($display_description)): ?>
<button class="igny8-menu-toggle igny8-description-toggle"
data-row-id="<?php echo esc_attr($id); ?>"
data-description="<?php echo esc_attr($display_description); ?>"
title="View description">
<span class="igny8-hamburger">
<span></span>
<span></span>
<span></span>
</span>
</button>
<?php endif; ?>
<?php if (!empty($image_prompts)): ?>
<button class="igny8-menu-toggle igny8-image-prompts-toggle"
data-row-id="<?php echo esc_attr($id); ?>"
data-image-prompts="<?php echo esc_attr($image_prompts); ?>"
title="View image prompts">
<span class="igny8-image-icon dashicons dashicons-format-image"></span>
</button>
<?php endif; ?>
</div>
</div>
</td>
<?php
}
// Special handling for title column in writer_tasks table
elseif ($col === 'title' && $tableId === 'writer_tasks') {
$description = isset($row['description']) ? $row['description'] : '';
// Format description for display (handle JSON vs plain text)
$display_description = '';
if (!empty($description)) {
$decoded = json_decode($description, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
// Format structured description for display
$display_description = igny8_format_structured_description_for_display($decoded);
} else {
// Use as plain text
$display_description = $description;
}
}
?>
<td>
<div class="igny8-title-with-badge">
<span class="igny8-title-text"><?php echo esc_html($value); ?></span>
<div class="igny8-title-actions">
<?php if (!empty($display_description)): ?>
<button class="igny8-menu-toggle igny8-description-toggle"
data-row-id="<?php echo esc_attr($id); ?>"
data-description="<?php echo esc_attr($display_description); ?>"
title="View description">
<span class="igny8-hamburger">
<span></span>
<span></span>
<span></span>
</span>
</button>
<?php endif; ?>
</div>
</div>
</td>
<?php
}
// Special handling for status column in planner_ideas table
elseif ($col === 'status' && $tableId === 'planner_ideas') {
$source = isset($row['source']) ? $row['source'] : '';
?>
<td>
<div class="igny8-status-with-badge">
<span class="igny8-status-text"><?php echo esc_html($value); ?></span>
<?php if ($source === 'Manual'): ?>
<span class="igny8-badge igny8-badge-dark-red">Manual</span>
<?php endif; ?>
</div>
</td>
<?php
} else {
?>
<td><?php echo esc_html($value); ?></td>
<?php
}
}
?>
<td class="igny8-align-center igny8-actions">
<button
class="igny8-icon-only igny8-icon-edit"
data-action="editRow"
data-row-id="<?php echo esc_attr($id); ?>"
data-table-id="<?php echo esc_attr($tableId); ?>"
title="Edit"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
<path d="m18.5 2.5 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
</svg>
</button>
<button class="igny8-icon-only igny8-icon-delete" data-action="deleteRow" data-row-id="<?php echo esc_attr($id); ?>" title="Delete">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3,6 5,6 21,6"></polyline>
<path d="m19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button>
</td>
</tr>
<?php
$table_body_html .= ob_get_clean();
}
} else {
// No records found - add empty state
ob_start();
?>
<tr>
<td colspan="<?php echo count($column_keys) + 2; ?>" class="igny8-align-center igny8-text-muted">
No records found
</td>
</tr>
<?php
$table_body_html = ob_get_clean();
}
$pagination_data = [
'current_page' => $page,
'total_items' => intval($total_count),
'per_page' => $per_page,
'total_pages' => $total_pages
];
return [
'table_body_html' => $table_body_html,
'pagination' => $pagination_data
];
}
// Render table function
function igny8_render_table($table_id, $columns = []) {
// Load table configuration with AI-specific modifications
$tables_config = require plugin_dir_path(__FILE__) . '../config/tables-config.php';
$GLOBALS['igny8_tables_config'] = $tables_config;
$table_config = igny8_get_dynamic_table_config($table_id);
// Set variables for component
$module = explode('_', $table_id)[0];
$tab = explode('_', $table_id)[1] ?? '';
// Use config columns if provided, otherwise use passed columns
$config_columns = $table_config['columns'] ?? [];
// Convert associative array to indexed array with 'key' field, including date columns
if (!empty($config_columns)) {
$columns = [];
foreach ($config_columns as $key => $column_config) {
// Check if this column should be humanized
$humanize_columns = $table_config['humanize_columns'] ?? [];
$should_humanize = in_array($key, $humanize_columns);
$columns[] = [
'key' => $key,
'label' => $column_config['label'] ?? ($should_humanize ? igny8_humanize_label($key) : $key),
'sortable' => $column_config['sortable'] ?? false,
'type' => $column_config['type'] ?? 'text'
];
}
}
// Start output buffering to capture HTML
ob_start();
?>
<!-- Data Table -->
<div class="igny8-table" data-table="<?php echo esc_attr($table_id); ?>">
<table id="<?php echo esc_attr($table_id); ?>" class="igny8-table">
<thead>
<tr>
<th>
<input type="checkbox" id="<?php echo esc_attr($table_id); ?>_select_all" class="igny8-checkbox">
</th>
<?php
// Render column headers dynamically based on table configuration
if (!empty($columns)) {
foreach ($columns as $column) {
$key = $column['key'];
$label = $column['label'];
$sortable = $column['sortable'] ?? false;
$sort_class = $sortable ? 'sortable' : '';
$sort_indicator = $sortable ? ' <span class="sort-indicator">↕</span>' : '';
?>
<th class="<?php echo esc_attr($sort_class); ?>" data-column="<?php echo esc_attr($key); ?>">
<?php echo esc_html($label); ?><?php echo $sort_indicator; ?>
</th>
<?php
}
} else {
// Fallback headers if no config
?>
<th class="sortable" data-column="keyword">Keyword <span class="sort-indicator">↕</span></th>
<th class="sortable" data-column="volume">Volume <span class="sort-indicator">↕</span></th>
<th class="sortable" data-column="kd">KD <span class="sort-indicator">↕</span></th>
<th class="sortable" data-column="cpc">CPC <span class="sort-indicator">↕</span></th>
<th class="sortable" data-column="intent">Intent <span class="sort-indicator">↕</span></th>
<th class="sortable" data-column="status">Status <span class="sort-indicator">↕</span></th>
<th class="sortable" data-column="cluster">Cluster <span class="sort-indicator">↕</span></th>
<?php
}
?>
<th>Actions</th>
</tr>
</thead>
<tbody id="table-<?php echo esc_attr($table_id); ?>-body">
<!-- Table data will be loaded dynamically via JavaScript -->
</tbody>
</table>
</div>
<?php
return ob_get_clean();
}
// Set default values
$table_id = $table_id ?? 'data_table';
$columns = $columns ?? [
['key' => 'content', 'label' => 'Content', 'sortable' => true],
['key' => 'status', 'label' => 'Status', 'sortable' => true],
['key' => 'date', 'label' => 'Date', 'sortable' => true]
];
$module = $module ?? '';
$tab = $tab ?? '';
// Debug state: Table HTML rendered
if (function_exists('igny8_debug_state')) {
igny8_debug_state('TABLE_HTML_RENDERED', true, 'Table HTML rendered for ' . $table_id);
}
?>

View File

@@ -0,0 +1,14 @@
<?php
/**
* ==============================
* 📁 Folder Scope Declaration
* ==============================
* Folder: /config/
* Purpose: Central config arrays (filters, tables, prompts)
* Rules:
* - Can be reused globally across all modules
* - Contains only configuration arrays
* - No executable code allowed
* - Must be pure data structures
* - Used by components and modules
*/

View File

@@ -0,0 +1,586 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : filters-config.php
* @location : /modules/config/filters-config.php
* @type : Config Array
* @scope : Global
* @allowed : Filter definitions, search configurations
* @reusability : Globally Reusable
* @notes : Central filter configuration for all modules
*/
// Prevent direct access only if not being included
if (!defined('ABSPATH') && !defined('IGNY8_INCLUDE_CONFIG')) {
exit;
}
return [
// Keywords Filters
'planner_keywords' => [
'keyword' => [
'type' => 'search',
'placeholder' => 'Search keywords...',
'field' => 'keyword'
],
'intent' => [
'type' => 'select',
'label' => 'Intent',
'field' => 'intent',
'options' => [
'informational' => 'Informational',
'navigational' => 'Navigational',
'transactional' => 'Transactional',
'commercial' => 'Commercial'
]
],
'status' => [
'type' => 'select',
'label' => 'Status',
'field' => 'status',
'options' => [
'unmapped' => 'Unmapped',
'mapped' => 'Mapped',
'queued' => 'Queued',
'published' => 'Published'
]
],
'difficulty' => [
'type' => 'select',
'label' => 'Difficulty',
'field' => 'difficulty',
'options' => [
'Very Easy' => 'Very Easy',
'Easy' => 'Easy',
'Medium' => 'Medium',
'Hard' => 'Hard',
'Very Hard' => 'Very Hard'
]
],
'search_volume' => [
'type' => 'range',
'label' => 'Volume',
'field' => 'search_volume',
'min' => 0,
'max' => 100000
]
],
// Clusters Filters
'planner_clusters' => [
'cluster_name' => [
'type' => 'search',
'placeholder' => 'Search clusters...',
'field' => 'cluster_name'
],
'sector_id' => [
'type' => 'select',
'label' => 'Sector',
'field' => 'sector_id',
'options' => 'dynamic_sectors' // Will be loaded via AJAX
],
'status' => [
'type' => 'select',
'label' => 'Status',
'field' => 'status',
'options' => [
'active' => 'Active',
'inactive' => 'Inactive',
'archived' => 'Archived'
]
],
'keyword_count' => [
'type' => 'range',
'label' => 'Keywords Count',
'field' => 'keyword_count',
'min' => 0,
'max' => 1000
]
],
// Ideas Filters
'planner_ideas' => [
'idea_title' => [
'type' => 'search',
'placeholder' => 'Search ideas...',
'field' => 'idea_title'
],
'content_structure' => [
'type' => 'select',
'label' => 'Content Structure',
'field' => 'content_structure',
'options' => [
'cluster_hub' => 'Cluster Hub',
'landing_page' => 'Landing Page',
'guide_tutorial' => 'Guide Tutorial',
'how_to' => 'How To',
'comparison' => 'Comparison',
'review' => 'Review',
'top_listicle' => 'Top Listicle',
'question' => 'Question',
'product_description' => 'Product Description',
'service_page' => 'Service Page',
'home_page' => 'Home Page'
]
],
'source' => [
'type' => 'select',
'label' => 'Source',
'field' => 'source',
'options' => [
'AI' => 'AI',
'Manual' => 'Manual'
]
],
'status' => [
'type' => 'select',
'label' => 'Status',
'field' => 'status',
'options' => [
'new' => 'New',
'scheduled' => 'Scheduled',
'published' => 'Published'
]
],
'keyword_cluster_id' => [
'type' => 'select',
'label' => 'Cluster',
'field' => 'keyword_cluster_id',
'options' => 'dynamic_clusters'
],
'estimated_word_count' => [
'type' => 'range',
'label' => 'Word Count',
'field' => 'estimated_word_count',
'min' => 0,
'max' => 5000
]
],
// Writer Tasks Filters (Content Queue / Tasks)
'writer_tasks' => [
'title' => [
'type' => 'search',
'placeholder' => 'Search tasks...',
'field' => 'title'
],
'keywords' => [
'type' => 'search',
'placeholder' => 'Search keywords...',
'field' => 'keywords'
],
'cluster_id' => [
'type' => 'select',
'label' => 'Cluster Name',
'field' => 'cluster_id',
'options' => 'dynamic_clusters'
],
'status' => [
'type' => 'select',
'label' => 'Status',
'field' => 'status',
'options' => [
'queued' => 'Queued',
'in_progress' => 'In Progress',
'completed' => 'Completed',
'cancelled' => 'Cancelled',
'draft' => 'Draft',
'review' => 'Review',
'published' => 'Published'
]
],
'content_type' => [
'type' => 'select',
'label' => 'Content Type',
'field' => 'content_type',
'options' => [
'blog_post' => 'Blog Post',
'landing_page' => 'Landing Page',
'product_page' => 'Product Page',
'guide_tutorial' => 'Guide Tutorial',
'news_article' => 'News Article',
'review' => 'Review',
'comparison' => 'Comparison',
'email' => 'Email',
'social_media' => 'Social Media'
]
],
'created_at' => [
'type' => 'date_range',
'label' => 'Queued Date Range',
'field' => 'created_at'
]
],
// Writer Drafts Filters (Content Generated)
'writer_drafts' => [
'title' => [
'type' => 'search',
'placeholder' => 'Search drafts...',
'field' => 'title'
],
'status' => [
'type' => 'select',
'label' => 'Status',
'field' => 'status',
'options' => [
'draft' => 'Draft',
'review' => 'Review'
]
],
'content_type' => [
'type' => 'select',
'label' => 'Content Type',
'field' => 'content_type',
'options' => [
'blog_post' => 'Blog Post',
'landing_page' => 'Landing Page',
'product_page' => 'Product Page',
'guide_tutorial' => 'Guide Tutorial',
'news_article' => 'News Article',
'review' => 'Review',
'comparison' => 'Comparison',
'email' => 'Email',
'social_media' => 'Social Media'
]
],
'cluster_id' => [
'type' => 'select',
'label' => 'Cluster',
'field' => 'cluster_id',
'options' => 'dynamic_clusters'
],
'meta_status' => [
'label' => 'Meta Status',
'type' => 'select',
'options' => [
'all' => 'All',
'complete' => 'Meta Present',
'missing' => 'Meta Missing'
]
],
'keywords' => [
'label' => 'Keywords',
'type' => 'text',
'searchable' => true
]
],
// Writer Published Filters (Live Content)
'writer_published' => [
'title' => [
'type' => 'search',
'placeholder' => 'Search published content...',
'field' => 'title'
],
'status' => [
'type' => 'select',
'label' => 'Status',
'field' => 'status',
'options' => [
'published' => 'Published'
]
],
'content_type' => [
'type' => 'select',
'label' => 'Content Type',
'field' => 'content_type',
'options' => [
'blog_post' => 'Blog Post',
'landing_page' => 'Landing Page',
'product_page' => 'Product Page',
'guide_tutorial' => 'Guide Tutorial',
'news_article' => 'News Article',
'review' => 'Review',
'comparison' => 'Comparison',
'email' => 'Email',
'social_media' => 'Social Media'
]
],
'cluster_id' => [
'type' => 'select',
'label' => 'Cluster',
'field' => 'cluster_id',
'options' => 'dynamic_clusters'
],
'meta_status' => [
'label' => 'Meta Status',
'type' => 'select',
'options' => [
'all' => 'All',
'complete' => 'Meta Present',
'missing' => 'Meta Missing'
]
],
'keywords' => [
'label' => 'Keywords',
'type' => 'text',
'searchable' => true
],
'created_at' => [
'type' => 'date_range',
'label' => 'Date Range',
'field' => 'created_at'
]
],
// Optimizer Audits Filters
'optimizer_audits' => [
'page_url' => [
'type' => 'search',
'placeholder' => 'Search pages...',
'field' => 'page_url'
],
'audit_status' => [
'type' => 'select',
'label' => 'Audit Status',
'field' => 'audit_status',
'options' => [
'pending' => 'Pending',
'in_progress' => 'In Progress',
'completed' => 'Completed',
'failed' => 'Failed'
]
],
'score_range' => [
'type' => 'range',
'label' => 'SEO Score',
'field' => 'seo_score',
'min' => 0,
'max' => 100
],
'last_audit' => [
'type' => 'date_range',
'label' => 'Last Audit',
'field' => 'last_audit_date'
]
],
// Linker Backlinks Filters
'linker_backlinks' => [
'target_url' => [
'type' => 'search',
'placeholder' => 'Search target URLs...',
'field' => 'target_url'
],
'source_domain' => [
'type' => 'search',
'placeholder' => 'Search source domains...',
'field' => 'source_domain'
],
'link_type' => [
'type' => 'select',
'label' => 'Link Type',
'field' => 'link_type',
'options' => [
'dofollow' => 'DoFollow',
'nofollow' => 'NoFollow',
'sponsored' => 'Sponsored',
'ugc' => 'UGC'
]
],
'status' => [
'type' => 'select',
'label' => 'Status',
'field' => 'status',
'options' => [
'active' => 'Active',
'lost' => 'Lost',
'pending' => 'Pending'
]
],
'domain_authority' => [
'type' => 'range',
'label' => 'Domain Authority',
'field' => 'domain_authority',
'min' => 0,
'max' => 100
]
],
// Writer Templates Filters
'writer_templates' => [
'prompt_name' => [
'type' => 'search',
'placeholder' => 'Search templates...',
'field' => 'prompt_name'
],
'prompt_type' => [
'type' => 'select',
'label' => 'Category',
'field' => 'prompt_type',
'options' => [
'content' => 'Blog',
'optimization' => 'Review',
'generation' => 'Product',
'custom' => 'Custom'
]
],
'is_active' => [
'type' => 'select',
'label' => 'Status',
'field' => 'is_active',
'options' => [
'1' => 'Active',
'0' => 'Draft'
]
],
'created_at' => [
'type' => 'date_range',
'label' => 'Created Date',
'field' => 'created_at'
]
],
// Optimizer Suggestions Filters
'optimizer_suggestions' => [
'page_url' => [
'type' => 'search',
'placeholder' => 'Search pages...',
'field' => 'page_url'
],
'suggestion_type' => [
'type' => 'select',
'label' => 'Suggestion Type',
'field' => 'suggestion_type',
'options' => [
'title_optimization' => 'Title Optimization',
'meta_description' => 'Meta Description',
'heading_structure' => 'Heading Structure',
'content_improvement' => 'Content Improvement',
'internal_linking' => 'Internal Linking'
]
],
'priority' => [
'type' => 'select',
'label' => 'Priority',
'field' => 'priority',
'options' => [
'high' => 'High',
'medium' => 'Medium',
'low' => 'Low'
]
],
'status' => [
'type' => 'select',
'label' => 'Status',
'field' => 'status',
'options' => [
'pending' => 'Pending',
'in_progress' => 'In Progress',
'completed' => 'Completed',
'dismissed' => 'Dismissed'
]
],
'impact_score' => [
'type' => 'range',
'label' => 'Impact Score',
'field' => 'impact_score',
'min' => 0,
'max' => 100
]
],
// Linker Campaigns Filters
'linker_campaigns' => [
'campaign_name' => [
'type' => 'search',
'placeholder' => 'Search campaigns...',
'field' => 'campaign_name'
],
'target_url' => [
'type' => 'search',
'placeholder' => 'Search target URLs...',
'field' => 'target_url'
],
'status' => [
'type' => 'select',
'label' => 'Status',
'field' => 'status',
'options' => [
'planning' => 'Planning',
'active' => 'Active',
'paused' => 'Paused',
'completed' => 'Completed',
'cancelled' => 'Cancelled'
]
],
'completion_percentage' => [
'type' => 'range',
'label' => 'Completion %',
'field' => 'completion_percentage',
'min' => 0,
'max' => 100
],
'start_date' => [
'type' => 'date_range',
'label' => 'Start Date',
'field' => 'start_date'
]
],
// Personalize Rewrites Filters
'personalize_rewrites' => [
'personalized_content' => [
'type' => 'search',
'placeholder' => 'Search personalized content...',
'field' => 'personalized_content'
],
'field_inputs' => [
'type' => 'search',
'placeholder' => 'Search field inputs...',
'field' => 'field_inputs'
],
'post_id' => [
'type' => 'search',
'placeholder' => 'Search by post ID...',
'field' => 'post_id'
],
'created_at' => [
'type' => 'date_range',
'label' => 'Created Date',
'field' => 'created_at'
]
],
// Personalize Tones Filters
'personalize_tones' => [
'tone_name' => [
'type' => 'search',
'placeholder' => 'Search tones...',
'field' => 'tone_name'
],
'category' => [
'type' => 'select',
'label' => 'Category',
'field' => 'category',
'options' => [
'business' => 'Business',
'creative' => 'Creative',
'technical' => 'Technical',
'marketing' => 'Marketing',
'educational' => 'Educational'
]
],
'status' => [
'type' => 'select',
'label' => 'Status',
'field' => 'status',
'options' => [
'active' => 'Active',
'inactive' => 'Inactive',
'draft' => 'Draft'
]
],
'usage_count' => [
'type' => 'range',
'label' => 'Usage Count',
'field' => 'usage_count',
'min' => 0,
'max' => 1000
]
]
];

View File

@@ -0,0 +1,638 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : forms-config.php
* @location : /modules/config/forms-config.php
* @type : Config Array
* @scope : Global
* @allowed : Form definitions, validation rules, field configurations
* @reusability : Globally Reusable
* @notes : Central form configuration for all modules
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
/**
* Get form configuration for a specific table
*
* @param string $table_id Table ID (e.g., 'planner_keywords')
* @return array|null Form configuration or null if not found
*/
function igny8_get_form_config($table_id) {
$form_configs = igny8_get_all_form_configs();
$GLOBALS['igny8_forms_config'] = $form_configs;
return igny8_get_dynamic_form_config($table_id);
}
/**
* Get all form configurations
*
* @return array All form configurations
*/
function igny8_get_all_form_configs() {
return [
'planner_keywords' => [
'fields' => [
[
'name' => 'keyword',
'type' => 'text',
'label' => 'Keyword',
'required' => true
],
[
'name' => 'search_volume',
'type' => 'number',
'label' => 'Search Volume',
'required' => false
],
[
'name' => 'difficulty',
'type' => 'select',
'label' => 'Difficulty',
'options' => [
'Very Easy' => 'Very Easy',
'Easy' => 'Easy',
'Medium' => 'Medium',
'Hard' => 'Hard',
'Very Hard' => 'Very Hard'
],
'required' => false
],
[
'name' => 'cpc',
'type' => 'number',
'label' => 'CPC',
'required' => false,
'step' => 0.01
],
[
'name' => 'intent',
'type' => 'select',
'label' => 'Intent',
'options' => [
'informational' => 'Informational',
'navigational' => 'Navigational',
'transactional' => 'Transactional',
'commercial' => 'Commercial'
],
'required' => false
],
[
'name' => 'status',
'type' => 'select',
'label' => 'Status',
'options' => [
'unmapped' => 'Unmapped',
'mapped' => 'Mapped',
'queued' => 'Queued',
'published' => 'Published'
],
'required' => true,
'default' => 'unmapped'
],
[
'name' => 'cluster_id',
'type' => 'select',
'label' => 'Cluster',
'source' => 'igny8_get_cluster_options',
'required' => false
]
],
'title' => 'Keyword',
'submit_text' => 'Save Keyword'
],
'planner_clusters' => [
'fields' => [
[
'name' => 'cluster_name',
'type' => 'text',
'label' => 'Cluster Name',
'required' => true
],
[
'name' => 'sector_id',
'type' => 'select',
'label' => 'Sector',
'source' => 'igny8_get_sector_options',
'required' => false
],
[
'name' => 'status',
'type' => 'select',
'label' => 'Status',
'options' => [
'active' => 'Active',
'inactive' => 'Inactive',
'archived' => 'Archived'
],
'required' => true,
'default' => 'active'
],
],
'title' => 'Cluster',
'submit_text' => 'Save Cluster'
],
'planner_ideas' => [
'fields' => [
[
'name' => 'idea_title',
'type' => 'text',
'label' => 'Idea Title',
'required' => true
],
[
'name' => 'idea_description',
'type' => 'textarea',
'label' => 'Description',
'required' => false
],
[
'name' => 'content_structure',
'type' => 'select',
'label' => 'Content Structure',
'options' => [
'cluster_hub' => 'Cluster Hub',
'landing_page' => 'Landing Page',
'guide_tutorial' => 'Guide Tutorial',
'how_to' => 'How To',
'comparison' => 'Comparison',
'review' => 'Review',
'top_listicle' => 'Top Listicle',
'question' => 'FAQ',
'product_description' => 'Product Description',
'service_page' => 'Service Page',
'home_page' => 'Home Page'
],
'required' => true,
'default' => 'cluster_hub'
],
[
'name' => 'content_type',
'type' => 'select',
'label' => 'Content Type',
'options' => [
'post' => 'Post',
'product' => 'Product',
'page' => 'Page',
'CPT' => 'Custom Post Type'
],
'required' => true,
'default' => 'post'
],
[
'name' => 'target_keywords',
'type' => 'textarea',
'label' => 'Target Keywords (comma-separated)',
'required' => false,
'placeholder' => 'Enter target keywords for this idea, separated by commas...',
'rows' => 3
],
[
'name' => 'image_prompts',
'type' => 'textarea',
'label' => 'Image Prompts (JSON)',
'required' => false,
'placeholder' => 'Enter image prompts as JSON...',
'rows' => 4,
'help_text' => 'JSON format: {"featured_image": "prompt", "in_article_image_1": "prompt", "in_article_image_2": "prompt"}'
],
[
'name' => 'keyword_cluster_id',
'type' => 'select',
'label' => 'Cluster',
'source' => 'igny8_get_cluster_options',
'required' => false
],
[
'name' => 'source',
'type' => 'select',
'label' => 'Source',
'options' => [
'AI' => 'AI',
'Manual' => 'Manual'
],
'required' => true,
'default' => 'AI'
],
[
'name' => 'status',
'type' => 'select',
'label' => 'Status',
'options' => [
'new' => 'New',
'scheduled' => 'Scheduled',
'published' => 'Published'
],
'required' => true,
'default' => 'new'
],
[
'name' => 'estimated_word_count',
'type' => 'number',
'label' => 'Estimated Words',
'required' => false
]
],
'title' => 'Content Idea',
'submit_text' => 'Save Idea'
],
'writer_tasks' => [
'fields' => [
[
'name' => 'title',
'type' => 'text',
'label' => 'Task Title',
'required' => true
],
[
'name' => 'cluster_id',
'type' => 'select',
'label' => 'Cluster Name',
'source' => 'igny8_get_cluster_options',
'required' => false
],
[
'name' => 'keywords',
'type' => 'text',
'label' => 'Keywords',
'required' => false
],
[
'name' => 'word_count',
'type' => 'number',
'label' => 'Word Count',
'required' => false
],
[
'name' => 'status',
'type' => 'select',
'label' => 'Status',
'options' => [
'queued' => 'Queued',
'in_progress' => 'In Progress',
'completed' => 'Completed',
'cancelled' => 'Cancelled',
'draft' => 'Draft',
'review' => 'Review',
'published' => 'Published'
],
'required' => true,
'default' => 'queued'
],
[
'name' => 'content_structure',
'type' => 'select',
'label' => 'Content Structure',
'options' => [
'cluster_hub' => 'Cluster Hub',
'landing_page' => 'Landing Page',
'guide_tutorial' => 'Guide Tutorial',
'how_to' => 'How To',
'comparison' => 'Comparison',
'review' => 'Review',
'top_listicle' => 'Top Listicle',
'question' => 'FAQ',
'product_description' => 'Product Description',
'service_page' => 'Service Page',
'home_page' => 'Home Page'
],
'required' => true,
'default' => 'cluster_hub'
],
[
'name' => 'content_type',
'type' => 'select',
'label' => 'Content Type',
'options' => [
'post' => 'Post',
'product' => 'Product',
'page' => 'Page',
'CPT' => 'Custom Post Type'
],
'required' => true,
'default' => 'post'
],
[
'name' => 'created_at',
'type' => 'text',
'label' => 'Created',
'required' => false,
'readonly' => true
]
],
'title' => 'Queue Task',
'submit_text' => 'Add to Queue'
],
'writer_drafts' => [
'fields' => [
[
'name' => 'title',
'type' => 'text',
'label' => 'Title',
'required' => true
],
[
'name' => 'status',
'type' => 'select',
'label' => 'Status',
'options' => [
'draft' => 'Draft'
],
'required' => true,
'default' => 'draft'
],
[
'name' => 'cluster_id',
'type' => 'select',
'label' => 'Cluster',
'source' => 'igny8_get_cluster_options',
'required' => false
],
[
'name' => 'content_structure',
'type' => 'select',
'label' => 'Content Structure',
'options' => [
'cluster_hub' => 'Cluster Hub',
'landing_page' => 'Landing Page',
'guide_tutorial' => 'Guide Tutorial',
'how_to' => 'How To',
'comparison' => 'Comparison',
'review' => 'Review',
'top_listicle' => 'Top Listicle',
'question' => 'FAQ',
'product_description' => 'Product Description',
'service_page' => 'Service Page',
'home_page' => 'Home Page'
],
'required' => false,
'default' => 'cluster_hub'
],
[
'name' => 'content_type',
'type' => 'select',
'label' => 'Content Type',
'options' => [
'post' => 'Post',
'product' => 'Product',
'page' => 'Page',
'CPT' => 'Custom Post Type'
],
'required' => false,
'default' => 'post'
],
[
'name' => 'meta_title',
'label' => 'Meta Title',
'type' => 'text',
'placeholder' => 'Enter SEO title...',
'maxlength' => 60
],
[
'name' => 'meta_description',
'label' => 'Meta Description',
'type' => 'textarea',
'placeholder' => 'Enter meta description...',
'maxlength' => 160
],
[
'name' => 'keywords',
'label' => 'Primary Keywords',
'type' => 'text',
'placeholder' => 'e.g., duvet covers, king size'
],
[
'name' => 'word_count',
'label' => 'Word Count',
'type' => 'number',
'readonly' => true
],
[
'name' => 'updated_at',
'type' => 'text',
'label' => 'Updated',
'required' => false,
'readonly' => true
]
],
'title' => 'Content Draft',
'submit_text' => 'Save Draft'
],
'writer_published' => [
'fields' => [
[
'name' => 'title',
'type' => 'text',
'label' => 'Title',
'required' => true
],
[
'name' => 'status',
'type' => 'select',
'label' => 'Status',
'options' => [
'published' => 'Published'
],
'required' => true,
'default' => 'published'
],
[
'name' => 'cluster_id',
'type' => 'select',
'label' => 'Cluster',
'source' => 'igny8_get_cluster_options',
'required' => false
],
[
'name' => 'content_structure',
'type' => 'select',
'label' => 'Content Structure',
'options' => [
'cluster_hub' => 'Cluster Hub',
'landing_page' => 'Landing Page',
'guide_tutorial' => 'Guide Tutorial',
'how_to' => 'How To',
'comparison' => 'Comparison',
'review' => 'Review',
'top_listicle' => 'Top Listicle',
'question' => 'FAQ',
'product_description' => 'Product Description',
'service_page' => 'Service Page',
'home_page' => 'Home Page'
],
'required' => false,
'default' => 'cluster_hub'
],
[
'name' => 'content_type',
'type' => 'select',
'label' => 'Content Type',
'options' => [
'post' => 'Post',
'product' => 'Product',
'page' => 'Page',
'CPT' => 'Custom Post Type'
],
'required' => false,
'default' => 'post'
],
[
'name' => 'meta_title',
'label' => 'Meta Title',
'type' => 'text',
'placeholder' => 'Enter SEO title...',
'maxlength' => 60
],
[
'name' => 'meta_description',
'label' => 'Meta Description',
'type' => 'textarea',
'placeholder' => 'Enter meta description...',
'maxlength' => 160
],
[
'name' => 'keywords',
'label' => 'Primary Keywords',
'type' => 'text',
'placeholder' => 'e.g., duvet covers, king size'
],
[
'name' => 'word_count',
'label' => 'Word Count',
'type' => 'number',
'readonly' => true
],
[
'name' => 'updated_at',
'type' => 'text',
'label' => 'Updated',
'required' => false,
'readonly' => true
]
],
'title' => 'Published Content',
'submit_text' => 'Save Published Content'
],
'writer_templates' => [
'fields' => [
[
'name' => 'prompt_name',
'type' => 'text',
'label' => 'Template Name',
'required' => true,
'placeholder' => 'Enter template name...'
],
[
'name' => 'prompt_type',
'type' => 'select',
'label' => 'Category',
'options' => [
'content' => 'Blog',
'optimization' => 'Review',
'generation' => 'Product',
'custom' => 'Custom'
],
'required' => true,
'default' => 'content'
],
[
'name' => 'is_active',
'type' => 'select',
'label' => 'Status',
'options' => [
'1' => 'Active',
'0' => 'Draft'
],
'required' => true,
'default' => '1'
],
[
'name' => 'prompt_text',
'type' => 'textarea',
'label' => 'Prompt Body',
'required' => true,
'rows' => 10,
'placeholder' => 'Enter the prompt template...'
],
[
'name' => 'variables',
'type' => 'textarea',
'label' => 'Variables (JSON)',
'required' => false,
'rows' => 5,
'placeholder' => '{"label": "Custom Label", "description": "Template description"}'
]
],
'title' => 'Content Template',
'submit_text' => 'Save Template'
],
'personalize_data' => [
'fields' => [
[
'name' => 'post_id',
'type' => 'number',
'label' => 'Post ID',
'required' => true
],
[
'name' => 'data_type',
'type' => 'text',
'label' => 'Data Type',
'required' => true
],
[
'name' => 'data',
'type' => 'textarea',
'label' => 'Data (JSON)',
'required' => true,
'rows' => 10
]
],
'title' => 'Personalization Data',
'submit_text' => 'Save Data'
],
'personalize_variations' => [
'fields' => [
[
'name' => 'post_id',
'type' => 'number',
'label' => 'Post ID',
'required' => true
],
[
'name' => 'fields_hash',
'type' => 'text',
'label' => 'Fields Hash',
'required' => true
],
[
'name' => 'fields_json',
'type' => 'textarea',
'label' => 'Fields JSON',
'required' => true,
'rows' => 5
],
[
'name' => 'content',
'type' => 'textarea',
'label' => 'Content',
'required' => true,
'rows' => 15
]
],
'title' => 'Content Variation',
'submit_text' => 'Save Variation'
]
];
}

View File

@@ -0,0 +1,150 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : import-export-config.php
* @location : /modules/config/import-export-config.php
* @type : Config Array
* @scope : Global
* @allowed : Import/export definitions, file handling configurations
* @reusability : Globally Reusable
* @notes : Central import/export configuration for all modules
*/
// Prevent direct access
if (!defined('ABSPATH') && !defined('IGNY8_INCLUDE_CONFIG')) {
exit;
}
return [
// PLANNER MODULE (4 submodules)
'planner_keywords' => [
'type' => 'keywords',
'singular' => 'Keyword',
'plural' => 'Keywords',
'template_file' => 'igny8_keywords_template.csv',
'columns' => ['keyword', 'search_volume', 'difficulty', 'cpc', 'intent', 'status', 'sector_id', 'cluster_id'],
'required_fields' => ['keyword']
],
'planner_clusters' => [
'type' => 'clusters',
'singular' => 'Cluster',
'plural' => 'Clusters',
'template_file' => 'igny8_clusters_template.csv',
'columns' => ['cluster_name', 'sector_id', 'status', 'keyword_count', 'total_volume', 'avg_difficulty', 'mapped_pages_count'],
'required_fields' => ['cluster_name']
],
'planner_ideas' => [
'type' => 'ideas',
'singular' => 'Idea',
'plural' => 'Ideas',
'template_file' => 'igny8_ideas_template.csv',
'columns' => ['idea_title', 'idea_description', 'content_structure', 'content_type', 'keyword_cluster_id', 'target_keywords', 'status', 'estimated_word_count'],
'required_fields' => ['idea_title']
],
// WRITER MODULE (3 submodules)
'writer_tasks' => [
'type' => 'tasks',
'singular' => 'Task',
'plural' => 'Tasks',
'template_file' => 'igny8_tasks_template.csv',
'columns' => ['title', 'description', 'content_type', 'cluster_id', 'priority', 'status', 'keywords', 'schedule_at'],
'required_fields' => ['title']
],
'writer_drafts' => [
'type' => 'tasks',
'singular' => 'Draft',
'plural' => 'Drafts',
'template_file' => 'igny8_tasks_template.csv',
'columns' => ['title', 'description', 'content_type', 'cluster_id', 'status', 'assigned_post_id'],
'required_fields' => ['title']
],
'writer_published' => [
'type' => 'tasks',
'singular' => 'Published Content',
'plural' => 'Published Content',
'template_file' => 'igny8_tasks_template.csv',
'columns' => ['title', 'description', 'content_type', 'cluster_id', 'status', 'assigned_post_id'],
'required_fields' => ['title']
],
'writer_templates' => [
'type' => 'templates',
'singular' => 'Template',
'plural' => 'Templates',
'template_file' => 'igny8_templates_template.csv',
'columns' => ['template_name', 'prompt_type', 'system_prompt', 'user_prompt', 'is_active'],
'required_fields' => ['template_name']
],
// OPTIMIZER MODULE (2 submodules)
'optimizer_audits' => [
'type' => 'audits',
'singular' => 'Audit',
'plural' => 'Audits',
'template_file' => 'igny8_audits_template.csv',
'columns' => ['page_id', 'audit_status', 'seo_score', 'issues_found', 'recommendations'],
'required_fields' => ['page_id']
],
'optimizer_suggestions' => [
'type' => 'suggestions',
'singular' => 'Suggestion',
'plural' => 'Suggestions',
'template_file' => 'igny8_suggestions_template.csv',
'columns' => ['audit_id', 'suggestion_type', 'priority', 'status', 'impact_level'],
'required_fields' => ['audit_id']
],
// LINKER MODULE (2 submodules)
'linker_backlinks' => [
'type' => 'backlinks',
'singular' => 'Backlink',
'plural' => 'Backlinks',
'template_file' => 'igny8_backlinks_template.csv',
'columns' => ['source_url', 'target_url', 'anchor_text', 'domain_authority', 'link_type', 'status'],
'required_fields' => ['source_url', 'target_url']
],
'linker_campaigns' => [
'type' => 'campaigns',
'singular' => 'Campaign',
'plural' => 'Campaigns',
'template_file' => 'igny8_campaigns_template.csv',
'columns' => ['campaign_name', 'target_url', 'status', 'backlink_count', 'live_links_count'],
'required_fields' => ['campaign_name']
],
// PERSONALIZE MODULE (4 submodules)
'personalize_rewrites' => [
'type' => 'rewrites',
'singular' => 'Rewrite',
'plural' => 'Rewrites',
'template_file' => 'igny8_rewrites_template.csv',
'columns' => ['post_id', 'tone_id', 'variation_content', 'created_at'],
'required_fields' => ['post_id']
],
'personalize_tones' => [
'type' => 'tones',
'singular' => 'Tone',
'plural' => 'Tones',
'template_file' => 'igny8_tones_template.csv',
'columns' => ['tone_name', 'tone_type', 'description', 'status', 'usage_count'],
'required_fields' => ['tone_name']
],
'personalize_data' => [
'type' => 'personalization_data',
'singular' => 'Data Entry',
'plural' => 'Data Entries',
'template_file' => 'igny8_personalization_data_template.csv',
'columns' => ['data_key', 'data_value', 'data_type', 'created_at'],
'required_fields' => ['data_key']
],
'personalize_variations' => [
'type' => 'variations',
'singular' => 'Variation',
'plural' => 'Variations',
'template_file' => 'igny8_variations_template.csv',
'columns' => ['post_id', 'field_name', 'variation_content', 'tone_id'],
'required_fields' => ['post_id', 'field_name']
]
];

View File

@@ -0,0 +1,581 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : kpi-config.php
* @location : /modules/config/kpi-config.php
* @type : Config Array
* @scope : Global
* @allowed : KPI definitions, metrics configurations
* @reusability : Globally Reusable
* @notes : Central KPI configuration for all modules
*/
// Prevent direct access only if not being included
if (!defined('ABSPATH') && !defined('IGNY8_INCLUDE_CONFIG')) {
exit;
}
return [
// Keywords KPIs
'planner_keywords' => [
'total_keywords' => [
'label' => 'Total Keywords',
'query' => 'SELECT COUNT(*) as count FROM {table_name}',
'color' => 'blue'
],
'mapped_keywords' => [
'label' => 'Mapped',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE status = "mapped"',
'color' => 'green'
],
'unmapped_keywords' => [
'label' => 'Unmapped',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE status = "unmapped"',
'color' => 'amber'
],
'total_volume' => [
'label' => 'Total Volume',
'query' => 'SELECT SUM(search_volume) as count FROM {table_name}',
'color' => 'purple'
],
'avg_difficulty' => [
'label' => 'Avg Difficulty',
'query' => 'SELECT ROUND(AVG(difficulty)) as count FROM {table_name}',
'color' => 'blue'
],
'high_volume_keywords' => [
'label' => 'High Volume (>1K)',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE search_volume > 1000',
'color' => 'green'
]
],
// Clusters KPIs
'planner_clusters' => [
'total_clusters' => [
'label' => 'Clusters',
'query' => 'SELECT COUNT(*) as count FROM {table_name}',
'color' => ''
],
'total_volume' => [
'label' => 'Volume',
'query' => 'SELECT SUM(total_volume) as count FROM {table_name}',
'color' => 'green'
],
'total_keywords' => [
'label' => 'Keywords',
'query' => 'SELECT SUM(keyword_count) as count FROM {table_name}',
'color' => 'amber'
],
'mapped_clusters' => [
'label' => 'Mapped',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE mapped_pages_count > 0',
'color' => 'purple'
],
'avg_keywords_per_cluster' => [
'label' => 'Avg Keywords/Cluster',
'query' => 'SELECT ROUND(AVG(keyword_count)) as count FROM {table_name}',
'color' => 'blue'
],
'high_volume_clusters' => [
'label' => 'High Volume Clusters',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE total_volume > 10000',
'color' => 'green'
]
],
// Ideas KPIs
'planner_ideas' => [
'total_ideas' => [
'label' => 'Ideas',
'query' => 'SELECT COUNT(*) as count FROM {table_name}',
'color' => ''
],
'new_ideas' => [
'label' => 'New',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE status = "new"',
'color' => 'green'
],
'scheduled_ideas' => [
'label' => 'Scheduled',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE status = "scheduled"',
'color' => 'amber'
],
'published_ideas' => [
'label' => 'Published',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE status = "published"',
'color' => 'purple'
],
'ai_generated' => [
'label' => 'AI Generated',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE source = "AI"',
'color' => 'blue'
],
'recent_ideas' => [
'label' => 'This Week',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)',
'color' => 'green'
]
],
// Planner Home KPIs (Main Dashboard)
'planner_home' => [
'total_keywords' => [
'label' => 'Keywords',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_keywords',
'color' => ''
],
'total_volume' => [
'label' => 'Volume',
'query' => 'SELECT SUM(search_volume) as count FROM {prefix}igny8_keywords',
'color' => 'green'
],
'total_clusters' => [
'label' => 'Clusters',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_clusters',
'color' => 'amber'
],
'total_ideas' => [
'label' => 'Ideas',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_content_ideas',
'color' => 'purple'
],
'high_volume_keywords' => [
'label' => 'High Vol (>1K)',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_keywords WHERE search_volume > 1000',
'color' => 'blue'
],
'recent_ideas' => [
'label' => 'This Week',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_content_ideas WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)',
'color' => 'teal'
],
'mapped_keywords' => [
'label' => 'Mapped Keywords',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_keywords WHERE status = "mapped"',
'color' => 'green'
],
'unmapped_keywords' => [
'label' => 'Unmapped Keywords',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_keywords WHERE status = "unmapped"',
'color' => 'amber'
],
'clusters_with_ideas' => [
'label' => 'Clusters With Ideas',
'query' => 'SELECT COUNT(DISTINCT keyword_cluster_id) as count FROM {prefix}igny8_content_ideas WHERE keyword_cluster_id IS NOT NULL',
'color' => 'green'
],
'queued_ideas' => [
'label' => 'Queued Ideas',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_content_ideas WHERE status = "scheduled"',
'color' => 'amber'
]
],
// Writer Home KPIs (Main Dashboard)
'writer_home' => [
'queued_tasks' => [
'label' => 'Queued Tasks',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_tasks WHERE status IN ("queued", "in_progress")',
'color' => 'blue'
],
'draft_tasks' => [
'label' => 'Drafts',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_tasks WHERE status IN ("draft", "review")',
'color' => 'amber'
],
'published_tasks' => [
'label' => 'Published',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_tasks WHERE status = "published"',
'color' => 'green'
],
'total_tasks' => [
'label' => 'Total Tasks',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_tasks',
'color' => ''
]
],
// Writer Tasks KPIs
'writer_tasks' => [
'total_ideas' => [
'label' => 'Ideas',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_content_ideas',
'color' => ''
],
'content_scheduled' => [
'label' => 'Content Scheduled',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_tasks WHERE status IN ("queued", "in_progress")',
'color' => 'green'
],
'written' => [
'label' => 'Written',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_tasks WHERE status IN ("draft", "review")',
'color' => 'amber'
],
'published' => [
'label' => 'Published',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_tasks WHERE status = "published"',
'color' => 'purple'
]
],
// Writer Drafts KPIs
'writer_drafts' => [
'total_ideas' => [
'label' => 'Ideas',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_content_ideas',
'color' => ''
],
'content_scheduled' => [
'label' => 'Content Scheduled',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_tasks WHERE status IN ("queued", "in_progress")',
'color' => 'green'
],
'written' => [
'label' => 'Written',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_tasks WHERE status IN ("draft", "review")',
'color' => 'amber'
],
'published' => [
'label' => 'Published',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_tasks WHERE status = "published"',
'color' => 'purple'
]
],
// Writer Published KPIs
'writer_published' => [
'total_ideas' => [
'label' => 'Ideas',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_content_ideas',
'color' => ''
],
'content_scheduled' => [
'label' => 'Content Scheduled',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_tasks WHERE status IN ("queued", "in_progress")',
'color' => 'green'
],
'written' => [
'label' => 'Written',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_tasks WHERE status IN ("draft", "review")',
'color' => 'amber'
],
'published' => [
'label' => 'Published',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_tasks WHERE status = "published"',
'color' => 'purple'
]
],
// Optimizer Audits KPIs
'optimizer_audits' => [
'total_audits' => [
'label' => 'Total Audits',
'query' => 'SELECT COUNT(*) as count FROM {table_name}',
'color' => 'blue'
],
'completed_audits' => [
'label' => 'Completed',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE audit_status = "completed"',
'color' => 'green'
],
'pending_audits' => [
'label' => 'Pending',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE audit_status = "pending"',
'color' => 'amber'
],
'avg_seo_score' => [
'label' => 'Avg SEO Score',
'query' => 'SELECT ROUND(AVG(seo_score)) as count FROM {table_name} WHERE audit_status = "completed"',
'color' => 'purple'
],
'total_issues' => [
'label' => 'Total Issues',
'query' => 'SELECT SUM(issues_found) as count FROM {table_name} WHERE audit_status = "completed"',
'color' => 'blue'
],
'recent_audits' => [
'label' => 'This Week',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)',
'color' => 'green'
]
],
// Writer Templates KPIs
'writer_templates' => [
'total_templates' => [
'label' => 'Total Templates',
'query' => 'SELECT COUNT(*) as count FROM {table_name}',
'color' => 'blue'
],
'active_templates' => [
'label' => 'Active',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE is_active = 1',
'color' => 'green'
],
'draft_templates' => [
'label' => 'Draft',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE is_active = 0',
'color' => 'gray'
],
'content_templates' => [
'label' => 'Blog Templates',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE prompt_type = "content"',
'color' => 'blue'
],
'product_templates' => [
'label' => 'Product Templates',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE prompt_type = "generation"',
'color' => 'green'
],
'popular_templates' => [
'label' => 'Popular (>10 uses)',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE usage_count > 10',
'color' => 'purple'
]
],
// Optimizer Suggestions KPIs
'optimizer_suggestions' => [
'total_suggestions' => [
'label' => 'Total Suggestions',
'query' => 'SELECT COUNT(*) as count FROM {table_name}',
'color' => 'blue'
],
'implemented' => [
'label' => 'Implemented',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE status = "implemented"',
'color' => 'green'
],
'pending' => [
'label' => 'Pending',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE status = "pending"',
'color' => 'amber'
],
'high_impact' => [
'label' => 'High Impact',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE impact_level = "high"',
'color' => 'purple'
],
'avg_improvement' => [
'label' => 'Avg Improvement',
'query' => 'SELECT ROUND(AVG(improvement_score)) as count FROM {table_name} WHERE status = "implemented"',
'color' => 'blue'
],
'recent_suggestions' => [
'label' => 'This Week',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)',
'color' => 'green'
]
],
// Linker Backlinks KPIs
'linker_backlinks' => [
'total_backlinks' => [
'label' => 'Total Backlinks',
'query' => 'SELECT COUNT(*) as count FROM {table_name}',
'color' => 'blue'
],
'active_backlinks' => [
'label' => 'Active',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE status = "active"',
'color' => 'green'
],
'lost_backlinks' => [
'label' => 'Lost',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE status = "lost"',
'color' => 'amber'
],
'avg_domain_authority' => [
'label' => 'Avg DA',
'query' => 'SELECT ROUND(AVG(domain_authority)) as count FROM {table_name} WHERE status = "active"',
'color' => 'purple'
],
'dofollow_links' => [
'label' => 'DoFollow Links',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE link_type = "dofollow" AND status = "active"',
'color' => 'blue'
],
'recent_backlinks' => [
'label' => 'This Week',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)',
'color' => 'green'
]
],
// Linker Campaigns KPIs
'linker_campaigns' => [
'total_campaigns' => [
'label' => 'Total Campaigns',
'query' => 'SELECT COUNT(*) as count FROM {table_name}',
'color' => 'blue'
],
'active_campaigns' => [
'label' => 'Active',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE status = "active"',
'color' => 'green'
],
'completed_campaigns' => [
'label' => 'Completed',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE status = "completed"',
'color' => 'amber'
],
'avg_links_per_campaign' => [
'label' => 'Avg Links/Campaign',
'query' => 'SELECT ROUND(AVG(links_acquired)) as count FROM {table_name}',
'color' => 'purple'
],
'success_rate' => [
'label' => 'Success Rate %',
'query' => 'SELECT ROUND((COUNT(CASE WHEN status = "completed" THEN 1 END) * 100.0 / COUNT(*))) as count FROM {table_name}',
'color' => 'blue'
],
'recent_campaigns' => [
'label' => 'This Week',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)',
'color' => 'green'
]
],
// Personalize Rewrites KPIs
'personalize_rewrites' => [
'total_rewrites' => [
'label' => 'Total Variations',
'query' => 'SELECT COUNT(*) as count FROM {table_name}',
'color' => 'blue'
],
'this_month_rewrites' => [
'label' => 'This Month',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE created_at >= DATE_FORMAT(NOW(), "%Y-%m-01")',
'color' => 'amber'
],
'total_ai_sessions' => [
'label' => 'AI Sessions',
'query' => 'SELECT COUNT(*) as count FROM {prefix}igny8_logs WHERE log_type = "field_detection" OR log_type = "content_generation"',
'color' => 'green'
],
'avg_sessions_per_rewrite' => [
'label' => 'Avg Sessions/Rewrite',
'query' => 'SELECT ROUND((SELECT COUNT(*) FROM {prefix}igny8_logs WHERE log_type = "field_detection" OR log_type = "content_generation") / GREATEST(COUNT(*), 1), 1) as count FROM {table_name}',
'color' => 'purple'
],
'recent_rewrites' => [
'label' => 'This Week',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)',
'color' => 'green'
],
'successful_rewrites' => [
'label' => 'Successful',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE status = "completed"',
'color' => 'blue'
]
],
// Personalize Tones KPIs
'personalize_tones' => [
'total_tones' => [
'label' => 'Total Tones',
'query' => 'SELECT COUNT(*) as count FROM {table_name}',
'color' => 'blue'
],
'active_tones' => [
'label' => 'Active',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE status = "active"',
'color' => 'green'
],
'custom_tones' => [
'label' => 'Custom',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE tone_type = "custom"',
'color' => 'amber'
],
'avg_usage_frequency' => [
'label' => 'Avg Usage',
'query' => 'SELECT ROUND(AVG(usage_count)) as count FROM {table_name}',
'color' => 'purple'
],
'popular_tones' => [
'label' => 'Popular (>50 uses)',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE usage_count > 50',
'color' => 'blue'
],
'recent_tones' => [
'label' => 'This Week',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)',
'color' => 'green'
]
],
// Personalization Data KPIs
'personalize_data' => [
'total_data_entries' => [
'label' => 'Total Data Entries',
'query' => 'SELECT COUNT(*) as count FROM {table_name}',
'color' => 'blue'
],
'personalization_data' => [
'label' => 'Personalization Data',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE data_type = "personalization"',
'color' => 'green'
],
'field_data' => [
'label' => 'Field Data',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE data_type = "fields"',
'color' => 'amber'
],
'recent_entries' => [
'label' => 'Recent (7 days)',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)',
'color' => 'purple'
],
'active_entries' => [
'label' => 'Active',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE status = "active"',
'color' => 'green'
],
'avg_usage_per_entry' => [
'label' => 'Avg Usage/Entry',
'query' => 'SELECT ROUND(AVG(usage_count)) as count FROM {table_name}',
'color' => 'blue'
]
],
// Personalization Variations KPIs
'personalize_variations' => [
'total_variations' => [
'label' => 'Total Variations',
'query' => 'SELECT COUNT(*) as count FROM {table_name}',
'color' => 'blue'
],
'unique_posts' => [
'label' => 'Unique Posts',
'query' => 'SELECT COUNT(DISTINCT post_id) as count FROM {table_name}',
'color' => 'green'
],
'recent_variations' => [
'label' => 'Recent (7 days)',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)',
'color' => 'amber'
],
'avg_variations_per_post' => [
'label' => 'Avg Variations/Post',
'query' => 'SELECT ROUND(COUNT(*) / COUNT(DISTINCT post_id), 2) as count FROM {table_name}',
'color' => 'purple'
],
'published_variations' => [
'label' => 'Published',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE status = "published"',
'color' => 'green'
],
'high_performing_variations' => [
'label' => 'High Performing',
'query' => 'SELECT COUNT(*) as count FROM {table_name} WHERE performance_score > 80',
'color' => 'blue'
]
]
];

View File

@@ -0,0 +1,989 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : tables-config.php
* @location : /modules/config/tables-config.php
* @type : Config Array
* @scope : Global
* @allowed : Table definitions, column structures, configuration data
* @reusability : Globally Reusable
* @notes : Central table configuration for all modules
*/
// Prevent direct access only if not being included
if (!defined('ABSPATH') && !defined('IGNY8_INCLUDE_CONFIG')) {
exit;
}
return [
// Keywords Table
'planner_keywords' => [
'table' => 'igny8_keywords',
'title' => 'Keywords Management',
'humanize_columns' => ['keyword', 'search_volume', 'difficulty', 'cpc', 'intent', 'status', 'cluster_id'],
'columns' => [
'keyword' => [
'label' => 'Keyword',
'type' => 'text',
'sortable' => true,
'searchable' => true
],
'search_volume' => [
'label' => 'Volume',
'type' => 'number',
'sortable' => true,
'format' => 'number'
],
'difficulty' => [
'label' => 'Difficulty',
'type' => 'number',
'sortable' => true,
'format' => 'difficulty_label'
],
'cpc' => [
'label' => 'CPC',
'type' => 'number',
'sortable' => true,
'decimal' => true
],
'intent' => [
'label' => 'Intent',
'type' => 'enum',
'options' => ['informational', 'navigational', 'transactional', 'commercial'],
'sortable' => true
],
'status' => [
'label' => 'Status',
'type' => 'enum',
'options' => ['unmapped', 'mapped', 'queued', 'published'],
'sortable' => true
],
'cluster_id' => [
'label' => 'Cluster',
'type' => 'lookup',
'source_field' => 'cluster_id',
'display_field' => 'cluster_name',
'sortable' => true,
'join_query' => 'LEFT JOIN {prefix}igny8_clusters c ON {table_name}.cluster_id = c.id',
'select_field' => 'c.cluster_name as cluster_name'
]
],
'pagination' => ['per_page' => 10, 'enabled' => true],
'search_field' => 'keyword',
'search_placeholder' => 'Search keywords...',
'actions' => ['delete_selected', 'export_selected', 'import', 'add_new'],
'bulk_actions' => ['delete', 'map'],
'row_actions' => ['edit', 'delete']
],
// Clusters Table
'planner_clusters' => [
'table' => 'igny8_clusters',
'title' => 'Clusters Management',
'humanize_columns' => ['cluster_name', 'sector_id', 'status', 'keyword_count', 'total_volume', 'avg_difficulty', 'mapped_pages_count', 'created_at'],
'columns' => [
'cluster_name' => [
'label' => 'Cluster Name',
'type' => 'text',
'sortable' => true,
'searchable' => true
],
'sector_id' => [
'label' => 'Sectors',
'type' => 'text',
'source_field' => 'sector_id',
'display_field' => 'sector_name',
'sortable' => true,
'join_query' => 'LEFT JOIN {prefix}igny8_sectors s ON {table_name}.sector_id = s.id',
'select_field' => 's.sector_name as sector_name'
],
'status' => [
'label' => 'Status',
'type' => 'enum',
'options' => ['active', 'inactive', 'archived'],
'sortable' => true
],
'keyword_count' => [
'label' => 'Keywords',
'type' => 'number',
'sortable' => true,
'calculated' => false
//'calculation_query' => 'SELECT COUNT(k.id) FROM {prefix}igny8_keywords k WHERE k.cluster_id = {table_name}.id'
],
'total_volume' => [
'label' => 'Total Volume',
'type' => 'number',
'sortable' => true,
'format' => 'number'
],
'avg_difficulty' => [
'label' => 'Avg KD',
'type' => 'number',
'sortable' => true,
'format' => 'difficulty_label'
],
'mapped_pages_count' => [
'label' => 'Mapped Pages',
'type' => 'number',
'sortable' => true,
'calculated' => false
],
'created_at' => [
'label' => 'Created',
'type' => 'date',
'sortable' => true,
'format' => 'date'
]
],
'pagination' => ['per_page' => 10, 'enabled' => true],
'search_field' => 'cluster_name',
'search_placeholder' => 'Search clusters...',
'actions' => ['delete_selected', 'export_selected', 'import', 'add_new'],
'bulk_actions' => ['delete', 'activate', 'deactivate'],
'row_actions' => ['edit', 'delete', 'view_keywords']
],
// Ideas Table
'planner_ideas' => [
'table' => 'igny8_content_ideas',
'title' => 'Content Ideas Management',
'humanize_columns' => ['idea_title', 'content_structure', 'content_type', 'target_keywords', 'keyword_cluster_id', 'status', 'estimated_word_count', 'created_at'],
'columns' => [
'idea_title' => [
'label' => 'Title',
'type' => 'text',
'sortable' => true,
'searchable' => true
],
'content_structure' => [
'label' => 'Structure',
'type' => 'enum',
'options' => ['cluster_hub', 'landing_page', 'guide_tutorial', 'how_to', 'comparison', 'review', 'top_listicle', 'question', 'product_description', 'service_page', 'home_page'],
'sortable' => true
],
'content_type' => [
'label' => 'Type',
'type' => 'enum',
'options' => ['post', 'product', 'page', 'CPT'],
'sortable' => true
],
'target_keywords' => [
'label' => 'Target Keywords',
'type' => 'text',
'sortable' => true,
'searchable' => true
],
'keyword_cluster_id' => [
'label' => 'Cluster',
'type' => 'lookup',
'source_field' => 'keyword_cluster_id',
'display_field' => 'cluster_name',
'sortable' => true,
'join_query' => 'LEFT JOIN {prefix}igny8_clusters c ON {table_name}.keyword_cluster_id = c.id',
'select_field' => 'c.cluster_name as cluster_name'
],
'status' => [
'label' => 'Status',
'type' => 'enum',
'options' => ['new', 'scheduled', 'published'],
'sortable' => true
],
'estimated_word_count' => [
'label' => 'Words',
'type' => 'number',
'sortable' => true
],
'created_at' => [
'label' => 'Created',
'type' => 'date',
'sortable' => true,
'format' => 'date'
]
],
'pagination' => ['per_page' => 10, 'enabled' => true],
'search_field' => 'idea_title',
'search_placeholder' => 'Search ideas...',
'actions' => ['delete_selected', 'export_selected', 'import', 'add_new'],
'bulk_actions' => ['delete', 'change_status', 'bulk_queue_to_writer'],
'row_actions' => ['edit', 'delete', 'create_draft', 'queue_to_writer']
],
// Writer Tasks Table (Content Queue / Tasks)
'writer_tasks' => [
'table' => 'igny8_tasks',
'title' => 'Content Queue / Tasks',
'humanize_columns' => ['title', 'cluster_id', 'keywords', 'word_count', 'status', 'content_structure', 'content_type', 'created_at'],
'default_filter' => [
'status' => ['queued', 'in_progress']
],
'columns' => [
'title' => [
'label' => 'Task Title',
'type' => 'text',
'sortable' => true,
'searchable' => true
],
'cluster_id' => [
'label' => 'Cluster Name',
'type' => 'lookup',
'source_field' => 'cluster_id',
'display_field' => 'cluster_name',
'sortable' => true,
'join_query' => 'LEFT JOIN {prefix}igny8_clusters c ON {table_name}.cluster_id = c.id',
'select_field' => 'c.cluster_name as cluster_name'
],
'keywords' => [
'label' => 'Keywords',
'type' => 'text',
'sortable' => true,
'searchable' => true
],
'word_count' => [
'label' => 'Word Count',
'type' => 'number',
'sortable' => true,
'format' => 'number'
],
'status' => [
'label' => 'Status',
'type' => 'enum',
'sortable' => true,
'options' => ['queued', 'in_progress', 'completed', 'cancelled', 'draft', 'review', 'published']
],
'content_structure' => [
'label' => 'Structure',
'type' => 'enum',
'sortable' => true,
'options' => ['cluster_hub', 'landing_page', 'guide_tutorial', 'how_to', 'comparison', 'review', 'top_listicle', 'question', 'product_description', 'service_page', 'home_page']
],
'content_type' => [
'label' => 'Type',
'type' => 'enum',
'sortable' => true,
'options' => ['post', 'product', 'page', 'CPT']
],
'created_at' => [
'label' => 'Created',
'type' => 'datetime',
'sortable' => true,
'format' => 'time_ago_created'
]
],
'pagination' => ['per_page' => 20, 'enabled' => true],
'search_field' => 'title',
'search_placeholder' => 'Search tasks...',
'filters' => [
'status' => [
'label' => 'Status',
'type' => 'select',
'options' => ['queued', 'in_progress']
],
'priority' => [
'label' => 'Priority',
'type' => 'select',
'options' => ['urgent', 'high', 'medium', 'low']
],
'content_structure' => [
'label' => 'Content Structure',
'type' => 'select',
'options' => ['cluster_hub', 'landing_page', 'guide_tutorial', 'how_to', 'comparison', 'review', 'top_listicle', 'question', 'product_description', 'service_page', 'home_page']
],
'content_type' => [
'label' => 'Content Type',
'type' => 'select',
'options' => ['post', 'product', 'page', 'CPT']
],
'cluster_id' => [
'label' => 'Cluster',
'type' => 'select',
'options' => 'dynamic_clusters'
]
],
'actions' => ['delete_selected', 'export_selected', 'add_new'],
'bulk_actions' => ['delete', 'mark_in_progress', 'move_to_drafts'],
'row_actions' => ['edit', 'delete']
],
// Writer Drafts Table (Content Generated)
'writer_drafts' => [
'table' => 'igny8_tasks',
'title' => 'Content Generated',
'humanize_columns' => ['title', 'cluster_id', 'status', 'content_structure', 'content_type', 'meta_title', 'meta_description', 'keywords', 'word_count', 'updated_at'],
'default_filter' => [
'status' => ['draft', 'review']
],
'columns' => [
'title' => [
'label' => 'Title',
'type' => 'text',
'sortable' => true,
'searchable' => true
],
'cluster_id' => [
'label' => 'Cluster',
'type' => 'lookup',
'source_field' => 'cluster_id',
'display_field' => 'cluster_name',
'sortable' => true,
'join_query' => 'LEFT JOIN {prefix}igny8_clusters c ON {table_name}.cluster_id = c.id',
'select_field' => 'c.cluster_name as cluster_name'
],
'status' => [
'label' => 'Status',
'type' => 'enum',
'sortable' => true,
'options' => ['draft', 'review']
],
'content_structure' => [
'label' => 'Structure',
'type' => 'enum',
'sortable' => true,
'options' => ['cluster_hub', 'landing_page', 'guide_tutorial', 'how_to', 'comparison', 'review', 'top_listicle', 'question', 'product_description', 'service_page', 'home_page']
],
'content_type' => [
'label' => 'Type',
'type' => 'enum',
'sortable' => true,
'options' => ['post', 'product', 'page', 'CPT']
],
'meta_title' => [
'label' => 'Meta Title',
'type' => 'text',
'sortable' => true,
'searchable' => true
],
'meta_description' => [
'label' => 'Meta Description',
'type' => 'text',
'sortable' => true,
'searchable' => true
],
'keywords' => [
'label' => 'Keywords',
'type' => 'text',
'sortable' => true,
'searchable' => true
],
'word_count' => [
'label' => 'Word Count',
'type' => 'number',
'sortable' => true
],
'updated_at' => [
'label' => 'Updated',
'type' => 'datetime',
'sortable' => true,
'format' => 'time_ago_updated'
]
],
'pagination' => ['per_page' => 20, 'enabled' => true],
'search_field' => 'title',
'search_placeholder' => 'Search drafts...',
'filters' => [
'status' => [
'label' => 'Status',
'type' => 'select',
'options' => ['draft', 'review']
],
'content_structure' => [
'label' => 'Content Structure',
'type' => 'select',
'options' => ['cluster_hub', 'landing_page', 'guide_tutorial', 'how_to', 'comparison', 'review', 'top_listicle', 'question', 'product_description', 'service_page', 'home_page']
],
'content_type' => [
'label' => 'Content Type',
'type' => 'select',
'options' => ['post', 'product', 'page', 'CPT']
],
'cluster_id' => [
'label' => 'Cluster',
'type' => 'select',
'options' => 'dynamic_clusters'
]
],
'actions' => ['delete_selected', 'publish_selected', 'export_selected', 'add_new'],
'bulk_actions' => ['delete', 'move_to_queue', 'publish'],
'row_actions' => ['edit', 'publish', 'delete']
],
// Writer Published Table (Live Content)
'writer_published' => [
'table' => 'igny8_tasks',
'title' => 'Live Content',
'humanize_columns' => ['title', 'status', 'cluster_id', 'content_structure', 'content_type', 'meta_title', 'meta_description', 'keywords', 'word_count', 'updated_at'],
'default_filter' => [
'status' => ['published']
],
'columns' => [
'title' => [
'label' => 'Title',
'type' => 'text',
'sortable' => true,
'searchable' => true
],
'status' => [
'label' => 'Status',
'type' => 'enum',
'sortable' => true,
'options' => ['published']
],
'cluster_id' => [
'label' => 'Cluster',
'type' => 'lookup',
'source_field' => 'cluster_id',
'display_field' => 'cluster_name',
'sortable' => true,
'join_query' => 'LEFT JOIN {prefix}igny8_clusters c ON {table_name}.cluster_id = c.id',
'select_field' => 'c.cluster_name as cluster_name'
],
'content_structure' => [
'label' => 'Structure',
'type' => 'enum',
'sortable' => true,
'options' => ['cluster_hub', 'landing_page', 'guide_tutorial', 'how_to', 'comparison', 'review', 'top_listicle', 'question', 'product_description', 'service_page', 'home_page']
],
'content_type' => [
'label' => 'Type',
'type' => 'enum',
'sortable' => true,
'options' => ['post', 'product', 'page', 'CPT']
],
'meta_title' => [
'label' => 'Meta Title',
'type' => 'text',
'source_meta' => '_igny8_meta_title'
],
'meta_description' => [
'label' => 'Meta Description',
'type' => 'text',
'source_meta' => '_igny8_meta_description'
],
'keywords' => [
'label' => 'Keywords',
'type' => 'text',
'source_meta' => '_igny8_primary_keywords'
],
'word_count' => [
'label' => 'Word Count',
'type' => 'number',
'source_meta' => '_igny8_word_count',
'sortable' => true
],
'updated_at' => [
'label' => 'Updated',
'type' => 'datetime',
'sortable' => true,
'format' => 'time_ago_updated'
]
],
'pagination' => ['per_page' => 20, 'enabled' => true],
'search_field' => 'title',
'search_placeholder' => 'Search published content...',
'filters' => [
'status' => [
'label' => 'Status',
'type' => 'select',
'options' => ['published']
],
'content_structure' => [
'label' => 'Content Structure',
'type' => 'select',
'options' => ['cluster_hub', 'landing_page', 'guide_tutorial', 'how_to', 'comparison', 'review', 'top_listicle', 'question', 'product_description', 'service_page', 'home_page']
],
'content_type' => [
'label' => 'Content Type',
'type' => 'select',
'options' => ['post', 'product', 'page', 'CPT']
],
'cluster_id' => [
'label' => 'Cluster',
'type' => 'select',
'options' => 'dynamic_clusters'
],
'created_at' => [
'label' => 'Date Range',
'type' => 'date_range',
'field' => 'created_at'
]
],
'actions' => ['delete_selected', 'export_selected', 'add_new'],
'bulk_actions' => ['delete', 'move_to_draft', 'unpublish'],
'row_actions' => ['edit', 'unpublish', 'delete']
],
// Optimizer Audits Table
'optimizer_audits' => [
'table' => 'igny8_audits',
'title' => 'SEO Audits Management',
'columns' => [
'page_url' => [
'label' => 'Page URL',
'type' => 'text',
'sortable' => true,
'searchable' => true,
'format' => 'url'
],
'seo_score' => [
'label' => 'SEO Score',
'type' => 'number',
'sortable' => true,
'format' => 'score'
],
'audit_status' => [
'label' => 'Status',
'type' => 'enum',
'options' => ['pending', 'in_progress', 'completed', 'failed'],
'sortable' => true
],
'issues_found' => [
'label' => 'Issues',
'type' => 'number',
'sortable' => true
],
'last_audit' => [
'label' => 'Last Audit',
'type' => 'date',
'sortable' => true,
'format' => 'datetime'
],
'next_audit' => [
'label' => 'Next Audit',
'type' => 'date',
'sortable' => true,
'format' => 'date'
]
],
'pagination' => ['per_page' => 10, 'enabled' => true],
'search_field' => 'page_url',
'search_placeholder' => 'Search pages...',
'actions' => ['delete_selected', 'export_selected', 'bulk_audit'],
'bulk_actions' => ['run_audit', 'schedule_audit'],
'row_actions' => ['view_details', 'run_audit', 'delete']
],
// Linker Backlinks Table
'linker_backlinks' => [
'table' => 'igny8_backlinks',
'title' => 'Backlinks Management',
'columns' => [
'target_url' => [
'label' => 'Target URL',
'type' => 'text',
'sortable' => true,
'searchable' => true,
'format' => 'url'
],
'source_domain' => [
'label' => 'Source Domain',
'type' => 'text',
'sortable' => true,
'searchable' => true
],
'link_type' => [
'label' => 'Link Type',
'type' => 'enum',
'options' => ['dofollow', 'nofollow', 'sponsored', 'ugc'],
'sortable' => true
],
'status' => [
'label' => 'Status',
'type' => 'enum',
'options' => ['active', 'lost', 'pending'],
'sortable' => true
],
'domain_authority' => [
'label' => 'DA',
'type' => 'number',
'sortable' => true
],
'anchor_text' => [
'label' => 'Anchor Text',
'type' => 'text',
'sortable' => true
],
'discovered_date' => [
'label' => 'Discovered',
'type' => 'date',
'sortable' => true,
'format' => 'date'
]
],
'pagination' => ['per_page' => 10, 'enabled' => true],
'search_field' => 'target_url',
'search_placeholder' => 'Search backlinks...',
'actions' => ['delete_selected', 'export_selected', 'recheck_links'],
'bulk_actions' => ['recheck', 'mark_lost'],
'row_actions' => ['edit', 'delete', 'recheck', 'view_source']
],
// Writer Templates Table (Prompts)
'writer_templates' => [
'table' => 'igny8_prompts',
'title' => 'Content Templates Management',
'columns' => [
'prompt_name' => [
'label' => 'Template Name',
'type' => 'text',
'sortable' => true,
'searchable' => true,
'source_field' => 'prompt_name',
'editable' => true
],
'category' => [
'label' => 'Category',
'type' => 'text',
'sortable' => true,
'source_field' => 'prompt_type',
'options' => [
'content' => 'Blog',
'optimization' => 'Review',
'generation' => 'Product',
'custom' => 'Custom'
]
],
'status' => [
'label' => 'Status',
'type' => 'badge',
'sortable' => true,
'source_field' => 'is_active',
'options' => [
'1' => ['label' => 'Active', 'color' => 'green'],
'0' => ['label' => 'Draft', 'color' => 'gray']
]
],
'label' => [
'label' => 'Label',
'type' => 'text',
'sortable' => true,
'source_field' => 'variables',
'format' => 'json_extract',
'json_path' => '$.label'
],
'prompt_text' => [
'label' => 'Prompt Body',
'type' => 'truncated_text',
'sortable' => false,
'source_field' => 'prompt_text',
'truncate_length' => 100,
'tooltip' => true
],
'created_at' => [
'label' => 'Created',
'type' => 'datetime',
'sortable' => true,
'format' => 'datetime'
]
],
'pagination' => ['per_page' => 15, 'enabled' => true],
'search_field' => 'prompt_name',
'search_placeholder' => 'Search templates...',
'actions' => ['delete_selected', 'export_selected', 'import', 'add_new'],
'bulk_actions' => ['delete', 'activate', 'deactivate'],
'row_actions' => ['edit', 'duplicate', 'delete']
],
// Optimizer Suggestions Table
'optimizer_suggestions' => [
'table' => 'igny8_suggestions',
'title' => 'SEO Suggestions Management',
'columns' => [
'page_url' => [
'label' => 'Page URL',
'type' => 'text',
'sortable' => true,
'searchable' => true,
'format' => 'url'
],
'suggestion_type' => [
'label' => 'Type',
'type' => 'enum',
'options' => ['title_optimization', 'meta_description', 'heading_structure', 'content_improvement', 'internal_linking'],
'sortable' => true
],
'priority' => [
'label' => 'Priority',
'type' => 'enum',
'options' => ['high', 'medium', 'low'],
'sortable' => true
],
'status' => [
'label' => 'Status',
'type' => 'enum',
'options' => ['pending', 'in_progress', 'completed', 'dismissed'],
'sortable' => true
],
'impact_score' => [
'label' => 'Impact Score',
'type' => 'number',
'sortable' => true,
'format' => 'score'
],
'created_date' => [
'label' => 'Created',
'type' => 'date',
'sortable' => true,
'format' => 'date'
]
],
'pagination' => ['per_page' => 10, 'enabled' => true],
'search_field' => 'page_url',
'search_placeholder' => 'Search pages...',
'actions' => ['delete_selected', 'export_selected', 'bulk_apply'],
'bulk_actions' => ['apply', 'dismiss', 'change_priority'],
'row_actions' => ['view_details', 'apply', 'dismiss']
],
// Linker Campaigns Table
'linker_campaigns' => [
'table' => 'igny8_campaigns',
'title' => 'Link Building Campaigns',
'columns' => [
'campaign_name' => [
'label' => 'Campaign Name',
'type' => 'text',
'sortable' => true,
'searchable' => true
],
'target_url' => [
'label' => 'Target URL',
'type' => 'text',
'sortable' => true,
'searchable' => true,
'format' => 'url'
],
'status' => [
'label' => 'Status',
'type' => 'enum',
'options' => ['planning', 'active', 'paused', 'completed', 'cancelled'],
'sortable' => true
],
'links_acquired' => [
'label' => 'Links Acquired',
'type' => 'number',
'sortable' => true
],
'target_links' => [
'label' => 'Target Links',
'type' => 'number',
'sortable' => true
],
'completion_percentage' => [
'label' => 'Completion %',
'type' => 'number',
'sortable' => true,
'format' => 'percentage'
],
'start_date' => [
'label' => 'Start Date',
'type' => 'date',
'sortable' => true,
'format' => 'date'
],
'end_date' => [
'label' => 'End Date',
'type' => 'date',
'sortable' => true,
'format' => 'date'
]
],
'pagination' => ['per_page' => 10, 'enabled' => true],
'search_field' => 'campaign_name',
'search_placeholder' => 'Search campaigns...',
'actions' => ['delete_selected', 'export_selected', 'import', 'add_new'],
'bulk_actions' => ['delete', 'activate', 'pause', 'complete'],
'row_actions' => ['edit', 'delete', 'view_progress', 'duplicate']
],
// Personalize Rewrites Table
'personalize_rewrites' => [
'table' => 'igny8_variations',
'title' => 'Content Variations Management',
'humanize_columns' => ['post_id', 'field_inputs', 'personalized_content', 'fields_hash', 'created_at'],
'columns' => [
'post_id' => [
'label' => 'Post',
'type' => 'text',
'sortable' => true,
'searchable' => true,
'join_query' => 'LEFT JOIN {prefix}posts p ON {table_name}.post_id = p.ID',
'select_field' => 'p.post_title as post_title',
'display_field' => 'post_title'
],
'field_inputs' => [
'label' => 'Field Inputs',
'type' => 'text',
'sortable' => false,
'searchable' => true,
'truncate' => 100
],
'personalized_content' => [
'label' => 'Personalized Content',
'type' => 'text',
'sortable' => false,
'searchable' => true,
'truncate' => 150
],
'fields_hash' => [
'label' => 'Fields Hash',
'type' => 'text',
'sortable' => true,
'searchable' => true,
'truncate' => 20
],
'created_at' => [
'label' => 'Created',
'type' => 'date',
'sortable' => true,
'format' => 'datetime'
]
],
'pagination' => ['per_page' => 20, 'enabled' => true],
'search_field' => 'personalized_content',
'search_placeholder' => 'Search personalized content...',
'actions' => ['delete_selected', 'export_selected', 'bulk_delete'],
'bulk_actions' => ['delete'],
'row_actions' => ['edit', 'delete', 'preview']
],
// Personalize Tones Table
'personalize_tones' => [
'table' => 'igny8_tones',
'title' => 'Tone Management',
'columns' => [
'tone_name' => [
'label' => 'Tone Name',
'type' => 'text',
'sortable' => true,
'searchable' => true
],
'description' => [
'label' => 'Description',
'type' => 'text',
'sortable' => false,
'searchable' => true,
'truncate' => 150
],
'category' => [
'label' => 'Category',
'type' => 'enum',
'options' => ['business', 'creative', 'technical', 'marketing', 'educational'],
'sortable' => true
],
'status' => [
'label' => 'Status',
'type' => 'enum',
'options' => ['active', 'inactive', 'draft'],
'sortable' => true
],
'usage_count' => [
'label' => 'Usage Count',
'type' => 'number',
'sortable' => true
],
'created_date' => [
'label' => 'Created',
'type' => 'date',
'sortable' => true,
'format' => 'date'
]
],
'pagination' => ['per_page' => 10, 'enabled' => true],
'search_field' => 'tone_name',
'search_placeholder' => 'Search tones...',
'actions' => ['delete_selected', 'export_selected', 'import', 'add_new'],
'bulk_actions' => ['delete', 'activate', 'deactivate'],
'row_actions' => ['edit', 'delete', 'duplicate', 'preview']
],
// Personalization Data Table
'personalize_data' => [
'table' => 'igny8_data',
'title' => 'Personalization Data',
'humanize_columns' => ['post_id', 'data_type', 'data', 'created_at'],
'columns' => [
'post_id' => [
'label' => 'Post ID',
'type' => 'number',
'sortable' => true,
'display_field' => 'post_title',
'join' => [
'table' => 'posts',
'on' => 'igny8_data.post_id = posts.ID',
'type' => 'LEFT'
]
],
'data_type' => [
'label' => 'Data Type',
'type' => 'text',
'sortable' => true,
'searchable' => true
],
'data' => [
'label' => 'Data',
'type' => 'json',
'sortable' => false,
'format' => 'json_preview'
],
'created_at' => [
'label' => 'Created',
'type' => 'date',
'sortable' => true,
'format' => 'datetime'
]
],
'pagination' => ['per_page' => 20, 'enabled' => true],
'search_field' => 'data_type',
'search_placeholder' => 'Search data types...',
'actions' => ['delete_selected', 'export_selected'],
'bulk_actions' => ['delete'],
'row_actions' => ['view', 'delete']
],
// Personalization Variations Table
'personalize_variations' => [
'table' => 'igny8_variations',
'title' => 'Content Variations',
'humanize_columns' => ['post_id', 'fields_hash', 'content', 'created_at'],
'columns' => [
'post_id' => [
'label' => 'Post ID',
'type' => 'number',
'sortable' => true,
'display_field' => 'post_title',
'join' => [
'table' => 'posts',
'on' => 'igny8_variations.post_id = posts.ID',
'type' => 'LEFT'
]
],
'fields_hash' => [
'label' => 'Fields Hash',
'type' => 'text',
'sortable' => true,
'format' => 'hash_preview'
],
'content' => [
'label' => 'Content',
'type' => 'text',
'sortable' => false,
'format' => 'content_preview'
],
'created_at' => [
'label' => 'Created',
'type' => 'date',
'sortable' => true,
'format' => 'datetime'
]
],
'pagination' => ['per_page' => 20, 'enabled' => true],
'search_field' => 'fields_hash',
'search_placeholder' => 'Search variations...',
'actions' => ['delete_selected', 'export_selected'],
'bulk_actions' => ['delete'],
'row_actions' => ['view', 'edit', 'delete']
]
];

View File

@@ -0,0 +1,841 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : docs.php
* @location : /modules/help/docs.php
* @type : Admin Page
* @scope : Module Only
* @allowed : Technical documentation, API reference, architecture overview
* @reusability : Single Use
* @notes : Technical documentation page for help module
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Start output buffering
ob_start();
?>
<!-- System Overview Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Igny8 AI SEO - Complete Technical Snapshot</h3>
<p class="igny8-card-subtitle">Comprehensive AI-powered SEO operating system for WordPress</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-site igny8-dashboard-icon-lg igny8-dashboard-icon-blue"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-module-overview">
<div class="igny8-module-section">
<h4>System Architecture</h4>
<p>Igny8 is a sophisticated WordPress plugin that combines AI automation with configuration-driven architecture to deliver enterprise-level SEO functionality.</p>
<ul>
<li><strong>Modular Design:</strong> Independent modules with clear interfaces</li>
<li><strong>AI-Centric:</strong> OpenAI integration for intelligent automation</li>
<li><strong>Configuration-Driven:</strong> All UI components render from configuration files</li>
<li><strong>Automation-First:</strong> CRON-based workflows for hands-off operation</li>
</ul>
</div>
<div class="igny8-module-section">
<h4>Core Modules</h4>
<p>Eight main modules provide comprehensive SEO functionality across the entire content lifecycle.</p>
<ul>
<li><strong>Planner:</strong> Keyword research, clustering, and content planning</li>
<li><strong>Writer:</strong> AI-powered content generation and task management</li>
<li><strong>Analytics:</strong> Performance tracking and SEO analytics</li>
<li><strong>Schedules:</strong> Automated task scheduling and CRON management</li>
</ul>
</div>
<div class="igny8-module-section">
<h4>Technical Stack</h4>
<p>Built on WordPress with advanced AI integration and modern web technologies.</p>
<ul>
<li><strong>Backend:</strong> PHP 7.4+, WordPress 5.0+, MySQL 5.7+</li>
<li><strong>AI Integration:</strong> OpenAI GPT-4, GPT-3.5-turbo</li>
<li><strong>Frontend:</strong> Vanilla JavaScript, CSS3, HTML5</li>
<li><strong>Database:</strong> 15 custom tables, WordPress integration</li>
</ul>
</div>
</div>
</div>
</div>
<!-- File Structure Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Complete File Structure</h3>
<p class="igny8-card-subtitle">Organized file tree with detailed descriptions</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-media-code igny8-dashboard-icon-lg igny8-dashboard-icon-purple"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-file-tree">
<pre><code>igny8-ai-seo/
├── igny8.php # Main plugin bootstrap and initialization
├── install.php # Database setup and plugin activation
├── uninstall.php # Plugin cleanup and data removal
├── igny8-wp-load-handler.php # CRON endpoint handler
├── CHANGELOG.md # Version history and changes
├── ai/ # AI Integration System (5 files)
│ ├── integration.php # API key setup and connection management
│ ├── modules-ai.php # Common AI interface for modules
│ ├── model-rates-config.php # AI model pricing and rate limits
│ ├── openai-api.php # OpenAI API integration and AI functions
│ └── prompts-library.php # AI prompts library and templates
├── assets/ # Frontend Assets
│ ├── css/
│ │ └── core.css # Main stylesheet (2000+ lines)
│ ├── js/
│ │ └── core.js # Main JavaScript (1000+ lines)
│ ├── templates/ # CSV templates for import/export
│ │ ├── igny8_clusters_template.csv
│ │ ├── igny8_ideas_template.csv
│ │ └── igny8_keywords_template.csv
│ └── ai-images/ # AI-generated images
├── core/ # Core System Files
│ ├── admin/ # Admin Interface System (7 files)
│ │ ├── ajax.php # Centralized AJAX endpoint management
│ │ ├── global-helpers.php # Global utility functions (50+ helpers)
│ │ ├── init.php # Admin initialization and settings registration
│ │ ├── menu.php # WordPress admin menu registration
│ │ ├── meta-boxes.php # WordPress meta boxes integration
│ │ ├── module-manager-class.php # Module management system
│ │ └── routing.php # Admin page routing and content rendering
│ ├── cron/ # CRON System (2 files)
│ │ ├── igny8-cron-handlers.php # CRON task handlers
│ │ └── igny8-cron-master-dispatcher.php # CRON master dispatcher
│ ├── db/ # Database System (2 files)
│ │ ├── db.php # Database operations, schema, and utilities
│ │ └── db-migration.php # Version-based migration system
│ ├── pages/ # Admin Page Templates (organized by module)
│ │ ├── analytics/ # Analytics module pages (2 files)
│ │ │ ├── analytics.php # Analytics and reporting interface
│ │ │ └── status.php # System status and health monitoring
│ │ ├── cron/ # CRON management pages
│ │ ├── help/ # Help and documentation pages (2 files)
│ │ │ ├── docs.php # Technical documentation page
│ │ │ └── help.php # User guide and support page
│ │ ├── settings/ # Settings module pages (4 files)
│ │ │ ├── general-settings.php # General plugin settings interface
│ │ │ ├── import-export.php # Data import/export interface
│ │ │ ├── integration.php # API integration settings interface
│ │ │ └── schedules.php # Scheduling and automation interface
│ │ └── thinker/ # Thinker module pages (5 files)
│ │ ├── image-testing.php # Image testing interface
│ │ ├── main.php # Thinker main interface
│ │ ├── profile.php # Thinker profile interface
│ │ ├── prompts.php # Prompts management interface
│ │ └── strategies.php # Strategies interface
│ └── global-layout.php # Master UI layout template
├── debug/ # Debug & Monitoring System (5 files)
│ ├── debug.php # Debug functionality (redirected to status)
│ ├── module-debug.php # Module-specific debugging utilities
│ ├── monitor-helpers.php # Monitoring helper functions
│ ├── system-testing.php # System testing utilities
│ └── temp-function-testing.php # Function testing utilities
├── docs/ # Documentation System (8 files)
│ ├── HOW_TO_ADD_COLUMN.md # Database column addition guide
│ ├── IGNY8_SNAPSHOT_V0.1.md # Complete plugin snapshot
│ ├── MASTER_ARCHITECTURE.md # Master architecture documentation
│ ├── how-tos/ # How-to guides (5 files)
│ │ ├── 01-adding-new-pages-and-modules.md
│ │ ├── 02-adding-new-modules-to-module-manager.md
│ │ ├── 03-auto-clustering-system.md
│ │ ├── cron-management.md
│ │ └── HOW_TO_ADD_COLUMN.md
│ └── parts/ # Architecture parts (2 files)
│ ├── AI_INTEGRATION_ARCHITECTURE.md
│ └── AUTOMATION_FLOWS.md
├── flows/ # Automation & Workflow System (3 files)
│ ├── sync-ajax.php # Automation-specific AJAX handlers
│ ├── sync-functions.php # Core automation logic and workflow functions
│ └── sync-hooks.php # Workflow hook definitions and registration
└── modules/ # Module System
├── components/ # Reusable UI Components (8 files)
│ ├── actions-tpl.php # Action buttons template
│ ├── export-modal-tpl.php # Export modal template
│ ├── filters-tpl.php # Filter controls template
│ ├── forms-tpl.php # Form rendering template
│ ├── import-modal-tpl.php # Import modal template
│ ├── kpi-tpl.php # KPI display template
│ ├── pagination-tpl.php # Pagination controls template
│ └── table-tpl.php # Data table template
├── config/ # Configuration Files (5 files)
│ ├── filters-config.php # Filter configuration definitions
│ ├── forms-config.php # Form configuration definitions
│ ├── import-export-config.php # Import/export configuration
│ ├── kpi-config.php # KPI configuration definitions
│ └── tables-config.php # Table configuration definitions
└── modules-pages/ # Module Page Interfaces
├── linker.php # Linker module interface
├── optimizer.php # Optimizer module interface
├── planner.php # Planner module interface
├── writer.php # Writer module interface
└── personalize/ # Personalization Module (7 files)
├── content-generation.php # Content generation interface
├── front-end.php # Frontend personalization
├── personalize.ajax # Personalization AJAX handlers
├── personalize.js # Personalization JavaScript
├── personalize.php # Personalize module main interface
├── rewrites.php # Content rewriting interface
└── Settings.php # Personalization settings</code></pre>
</div>
</div>
</div>
<!-- Database Schema Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Database Architecture</h3>
<p class="igny8-card-subtitle">15 custom tables with comprehensive relationships</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-database igny8-dashboard-icon-lg igny8-dashboard-icon-green"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-module-overview">
<div class="igny8-module-section">
<h4>Core Data Tables</h4>
<ul>
<li><strong>igny8_keywords</strong> - Keyword research data with metrics</li>
<li><strong>igny8_clusters</strong> - Content topic groupings with stored metrics</li>
<li><strong>igny8_content_ideas</strong> - AI-generated content concepts</li>
<li><strong>igny8_tasks</strong> - Writer workflow management</li>
<li><strong>igny8_variations</strong> - Personalization content cache</li>
</ul>
</div>
<div class="igny8-module-section">
<h4>Analytics & Tracking</h4>
<ul>
<li><strong>igny8_logs</strong> - System audit trail and AI event logging</li>
<li><strong>igny8_ai_queue</strong> - AI processing queue with retry logic</li>
<li><strong>igny8_campaigns</strong> - Link building campaign management</li>
<li><strong>igny8_backlinks</strong> - Backlink monitoring and tracking</li>
</ul>
</div>
<div class="igny8-module-section">
<h4>WordPress Integration</h4>
<ul>
<li><strong>wp_options</strong> - Plugin settings (38+ options)</li>
<li><strong>wp_posts</strong> - Generated WordPress content</li>
<li><strong>wp_postmeta</strong> - Custom post meta fields (6 fields)</li>
<li><strong>wp_terms</strong> - Custom taxonomies (sectors, clusters)</li>
</ul>
</div>
</div>
<div class="igny8-data-flow">
<h4>Data Flow Architecture</h4>
<pre><code>Keywords → Clusters → Ideas → Tasks → WordPress Posts
↓ ↓ ↓ ↓
Mapping → Posts ← Variations (Personalization)
Campaigns → Sites → Backlinks</code></pre>
</div>
</div>
</div>
<!-- AI Integration Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>AI Integration System</h3>
<p class="igny8-card-subtitle">OpenAI integration with cost tracking and automation</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-robot igny8-dashboard-icon-lg igny8-dashboard-icon-orange"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-module-overview">
<div class="igny8-module-section">
<h4>AI Functions</h4>
<ul>
<li><strong>Content Generation:</strong> Blog posts, landing pages, product descriptions</li>
<li><strong>Keyword Analysis:</strong> Intent detection, difficulty scoring, clustering</li>
<li><strong>SEO Optimization:</strong> Meta descriptions, title optimization</li>
<li><strong>Personalization:</strong> Audience-specific content variations</li>
</ul>
</div>
<div class="igny8-module-section">
<h4>Model Configuration</h4>
<ul>
<li><strong>GPT-4:</strong> Primary model for complex tasks</li>
<li><strong>GPT-3.5-turbo:</strong> Fallback for cost optimization</li>
<li><strong>Rate Limiting:</strong> Automatic retry with exponential backoff</li>
<li><strong>Cost Tracking:</strong> Daily budget limits and usage monitoring</li>
</ul>
</div>
<div class="igny8-module-section">
<h4>AI Queue System</h4>
<ul>
<li><strong>Queue Processing:</strong> Background AI task processing</li>
<li><strong>Retry Logic:</strong> Automatic retry for failed requests</li>
<li><strong>Priority System:</strong> Task prioritization for efficient processing</li>
<li><strong>Error Handling:</strong> Comprehensive error logging and recovery</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Automation Workflows Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Automation Workflows</h3>
<p class="igny8-card-subtitle">Event-driven automation with CRON scheduling</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-update igny8-dashboard-icon-lg igny8-dashboard-icon-purple"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-workflow-examples">
<div class="igny8-workflow">
<h4>Keyword Processing Workflow</h4>
<pre><code>// When keywords are imported/updated
igny8_handle_keyword_cluster_update($keyword_id) {
// Update cluster metrics
igny8_update_cluster_metrics($cluster_id);
// Trigger AI clustering if enabled
if (ai_enabled) {
igny8_ajax_ai_cluster_keywords($keyword_ids);
}
}</code></pre>
</div>
<div class="igny8-workflow">
<h4>Content Generation Workflow</h4>
<pre><code>// When content ideas are created
igny8_create_task_from_idea($idea_id) {
// Create writer task
$task_id = create_task($idea_data);
// Generate content if AI enabled
if (ai_enabled) {
igny8_ajax_ai_generate_content($task_id);
}
// Update metrics
igny8_update_idea_metrics($idea_id);
}</code></pre>
</div>
<div class="igny8-workflow">
<h4>Cluster Management Workflow</h4>
<pre><code>// When clusters are created/updated
igny8_auto_create_cluster_term($cluster_id) {
// Create WordPress taxonomy term
$term_id = wp_insert_term($cluster_name, 'clusters');
// Link cluster to term
update_cluster_term_id($cluster_id, $term_id);
// Update metrics
igny8_update_cluster_metrics($cluster_id);
}</code></pre>
</div>
</div>
</div>
</div>
<!-- Configuration System Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Configuration System</h3>
<p class="igny8-card-subtitle">Configuration-driven UI with reusable components</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-tools igny8-dashboard-icon-lg igny8-dashboard-icon-blue"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-module-overview">
<div class="igny8-module-section">
<h4>Table Configuration</h4>
<p>Dynamic table rendering with sorting, filtering, and pagination based on configuration files.</p>
<ul>
<li><strong>Column Definitions:</strong> Field types, labels, and display options</li>
<li><strong>Sorting & Filtering:</strong> Configurable sort and filter options</li>
<li><strong>Actions:</strong> Bulk operations and individual record actions</li>
<li><strong>Pagination:</strong> Configurable page sizes and navigation</li>
</ul>
</div>
<div class="igny8-module-section">
<h4>Form Configuration</h4>
<p>Dynamic form generation with validation and field types based on configuration.</p>
<ul>
<li><strong>Field Types:</strong> Text, number, select, textarea, date, etc.</li>
<li><strong>Validation:</strong> Required fields, data types, and custom validation</li>
<li><strong>Lookup Fields:</strong> Foreign key relationships and dropdown options</li>
<li><strong>Conditional Logic:</strong> Show/hide fields based on other field values</li>
</ul>
</div>
<div class="igny8-module-section">
<h4>KPI Configuration</h4>
<p>Dynamic metrics display with charts and trend indicators based on configuration.</p>
<ul>
<li><strong>Metric Types:</strong> Count, sum, average, percentage calculations</li>
<li><strong>Visualization:</strong> Charts, graphs, and trend indicators</li>
<li><strong>Filtering:</strong> Date ranges and conditional filtering</li>
<li><strong>Real-time Updates:</strong> Live data updates and caching</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Security & Performance Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Security & Performance</h3>
<p class="igny8-card-subtitle">Enterprise-level security and optimization</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-shield igny8-dashboard-icon-lg igny8-dashboard-icon-red"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-module-overview">
<div class="igny8-module-section">
<h4>Security Measures</h4>
<ul>
<li><strong>Nonce Verification:</strong> All AJAX requests protected with WordPress nonces</li>
<li><strong>Capability Checks:</strong> User permission validation for all operations</li>
<li><strong>Data Sanitization:</strong> All input data sanitized and validated</li>
<li><strong>SQL Injection Protection:</strong> Prepared statements for all database queries</li>
</ul>
</div>
<div class="igny8-module-section">
<h4>Performance Optimizations</h4>
<ul>
<li><strong>Conditional Loading:</strong> Admin assets only loaded when needed</li>
<li><strong>Database Indexing:</strong> Optimized indexes on frequently queried fields</li>
<li><strong>Caching:</strong> WordPress transients for expensive operations</li>
<li><strong>Lazy Loading:</strong> AJAX-based data loading for large datasets</li>
</ul>
</div>
<div class="igny8-module-section">
<h4>Monitoring & Debugging</h4>
<ul>
<li><strong>Real-time Monitoring:</strong> Live system health monitoring</li>
<li><strong>Module Debug:</strong> Individual module performance tracking</li>
<li><strong>Error Logging:</strong> Comprehensive error tracking and reporting</li>
<li><strong>Performance Metrics:</strong> Response times and resource usage</li>
</ul>
</div>
</div>
</div>
</div>
<!-- API Reference Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>API Reference</h3>
<p class="igny8-card-subtitle">Complete function and endpoint documentation</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-book igny8-dashboard-icon-lg igny8-dashboard-icon-green"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-api-reference">
<div class="igny8-api-section">
<h4>Core Functions</h4>
<div class="igny8-grid-2">
<div>
<h5>Database Functions</h5>
<ul>
<li><strong>igny8_create_all_tables()</strong> - Create all database tables</li>
<li><strong>igny8_register_taxonomies()</strong> - Register custom taxonomies</li>
<li><strong>igny8_register_post_meta()</strong> - Register custom post meta</li>
<li><strong>igny8_install_database()</strong> - Complete plugin installation</li>
</ul>
</div>
<div>
<h5>Admin Functions</h5>
<ul>
<li><strong>igny8_get_cluster_options()</strong> - Get cluster dropdown options</li>
<li><strong>igny8_get_sector_options()</strong> - Get sector dropdown options</li>
<li><strong>igny8_render_table()</strong> - Render dynamic tables</li>
<li><strong>igny8_render_filters()</strong> - Render filter controls</li>
</ul>
</div>
</div>
</div>
<div class="igny8-api-section">
<h4>AI Functions</h4>
<div class="igny8-grid-2">
<div>
<h5>Content Generation</h5>
<ul>
<li><strong>igny8_generate_blog_post()</strong> - Generate blog post content</li>
<li><strong>igny8_generate_landing_page()</strong> - Generate landing page content</li>
<li><strong>igny8_generate_product_description()</strong> - Generate product content</li>
<li><strong>igny8_generate_seo_meta()</strong> - Generate SEO meta data</li>
</ul>
</div>
<div>
<h5>AI Analysis</h5>
<ul>
<li><strong>igny8_ai_analyze_keywords()</strong> - Analyze keywords using AI</li>
<li><strong>igny8_ai_cluster_keywords()</strong> - Cluster keywords using AI</li>
<li><strong>igny8_ai_generate_ideas()</strong> - Generate content ideas</li>
<li><strong>igny8_ai_optimize_content()</strong> - Optimize existing content</li>
</ul>
</div>
</div>
</div>
<div class="igny8-api-section">
<h4>Automation Functions</h4>
<div class="igny8-grid-2">
<div>
<h5>Workflow Functions</h5>
<ul>
<li><strong>igny8_update_cluster_metrics()</strong> - Update cluster metrics</li>
<li><strong>igny8_update_idea_metrics()</strong> - Update idea metrics</li>
<li><strong>igny8_workflow_triggers()</strong> - Trigger workflow automation</li>
<li><strong>igny8_bulk_delete_keywords()</strong> - Bulk delete keywords</li>
</ul>
</div>
<div>
<h5>AJAX Endpoints</h5>
<ul>
<li><strong>wp_ajax_igny8_get_table_data</strong> - Get table data</li>
<li><strong>wp_ajax_igny8_save_record</strong> - Save/update record</li>
<li><strong>wp_ajax_igny8_ai_generate_content</strong> - AI content generation</li>
<li><strong>wp_ajax_igny8_bulk_action</strong> - Perform bulk actions</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Development Workflow Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Development Workflow</h3>
<p class="igny8-card-subtitle">Guidelines for extending and maintaining the plugin</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-editor-code igny8-dashboard-icon-lg igny8-dashboard-icon-purple"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-module-overview">
<div class="igny8-module-section">
<h4>Adding New Modules</h4>
<ol>
<li>Create module page in <code>modules/modules-pages/</code></li>
<li>Update module manager in <code>core/admin/module-manager-class.php</code></li>
<li>Add table, form, and filter configurations</li>
<li>Register routes in <code>core/admin/routing.php</code></li>
<li>Add menu items in <code>core/admin/menu.php</code></li>
</ol>
</div>
<div class="igny8-module-section">
<h4>Adding New Tables</h4>
<ol>
<li>Add database schema to <code>core/db/db.php</code></li>
<li>Create migration in <code>core/db/db-migration.php</code></li>
<li>Add table configuration to <code>modules/config/tables-config.php</code></li>
<li>Add form configuration to <code>modules/config/forms-config.php</code></li>
<li>Add filter configuration to <code>modules/config/filters-config.php</code></li>
</ol>
</div>
<div class="igny8-module-section">
<h4>Adding AI Features</h4>
<ol>
<li>Add prompt template to <code>ai/prompts-library.php</code></li>
<li>Add AI handler to <code>ai/modules-ai.php</code></li>
<li>Add queue processing to <code>flows/sync-functions.php</code></li>
<li>Add AJAX endpoint to <code>flows/sync-ajax.php</code></li>
</ol>
</div>
</div>
</div>
</div>
<style>
.igny8-help-page {
max-width: 1200px;
margin: 0 auto;
}
.igny8-standard-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.igny8-card-header-content {
display: flex;
align-items: center;
justify-content: space-between;
}
.igny8-card-title-text h3 {
margin: 0 0 8px 0;
font-size: 24px;
font-weight: 600;
}
.igny8-card-subtitle {
margin: 0;
font-size: 16px;
opacity: 0.9;
}
.igny8-dashboard-icon-lg {
font-size: 32px;
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
}
.igny8-dashboard-icon-blue { color: #3b82f6; }
.igny8-dashboard-icon-green { color: #10b981; }
.igny8-dashboard-icon-purple { color: #8b5cf6; }
.igny8-dashboard-icon-orange { color: #f59e0b; }
.igny8-dashboard-icon-red { color: #ef4444; }
.igny8-module-overview {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
}
.igny8-module-section {
background: #f8fafc;
padding: 20px;
border-radius: 8px;
border-left: 4px solid #3b82f6;
}
.igny8-module-section h4 {
margin: 0 0 12px 0;
color: #1f2937;
font-size: 18px;
}
.igny8-module-section p {
margin: 0 0 15px 0;
color: #6b7280;
line-height: 1.6;
}
.igny8-module-section ul {
margin: 0;
padding-left: 20px;
}
.igny8-module-section li {
margin-bottom: 8px;
color: #374151;
line-height: 1.5;
}
.igny8-file-tree {
background: #1f2937;
color: #f9fafb;
padding: 20px;
border-radius: 8px;
overflow-x: auto;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
line-height: 1.5;
}
.igny8-file-tree pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
.igny8-data-flow {
margin-top: 20px;
padding: 20px;
background: #f8fafc;
border-radius: 8px;
border-left: 4px solid #10b981;
}
.igny8-data-flow pre {
background: #1f2937;
color: #f9fafb;
padding: 15px;
border-radius: 6px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
overflow-x: auto;
}
.igny8-workflow-examples {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 20px;
}
.igny8-workflow {
background: #f8fafc;
padding: 20px;
border-radius: 8px;
border-left: 4px solid #8b5cf6;
}
.igny8-workflow h4 {
margin: 0 0 15px 0;
color: #1f2937;
font-size: 16px;
}
.igny8-workflow pre {
background: #1f2937;
color: #f9fafb;
padding: 15px;
border-radius: 6px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 12px;
overflow-x: auto;
margin: 0;
}
.igny8-api-reference {
display: flex;
flex-direction: column;
gap: 30px;
}
.igny8-api-section {
background: #f8fafc;
padding: 20px;
border-radius: 8px;
border-left: 4px solid #3b82f6;
}
.igny8-api-section h4 {
margin: 0 0 20px 0;
color: #1f2937;
font-size: 18px;
}
.igny8-api-section h5 {
margin: 0 0 10px 0;
color: #374151;
font-size: 14px;
font-weight: 600;
}
.igny8-api-section ul {
margin: 0;
padding-left: 20px;
}
.igny8-api-section li {
margin-bottom: 6px;
color: #4b5563;
line-height: 1.4;
font-size: 13px;
}
.igny8-api-section code {
background: #e5e7eb;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 12px;
}
@media (max-width: 768px) {
.igny8-card-header-content {
flex-direction: column;
text-align: center;
gap: 15px;
}
.igny8-module-overview,
.igny8-workflow-examples {
grid-template-columns: 1fr;
}
.igny8-file-tree {
font-size: 11px;
}
}
</style>

View File

@@ -0,0 +1,122 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : function-testing.php
* @location : /modules/help/function-testing.php
* @type : Debug Tool
* @scope : Module Only
* @allowed : Function testing, development tools, debugging functions
* @reusability : Single Use
* @notes : Function testing interface for help module (dev-only)
*/
// Note: AJAX handlers are now registered in core/admin/ajax.php
// This file only contains the HTML interface for testing
?>
<div class="wrap">
<h1>Test Page</h1>
<p>This is a test page</p>
<h2>AJAX Text Input Test</h2>
<form id="ajax-text-form">
<input type="hidden" id="ajax_text_nonce" name="ajax_text_nonce" value="<?php echo wp_create_nonce('ajax_text_action'); ?>" />
<table class="form-table">
<tr>
<th scope="row">
<label for="ajax_text_input">Test Text Input</label>
</th>
<td>
<input type="text" id="ajax_text_input" name="ajax_text_input" value="<?php echo esc_attr(get_option('igny8_ajax_test_text', '')); ?>" class="regular-text" placeholder="Enter some text to save via AJAX" />
<p class="description">This text will be saved using AJAX without page reload</p>
</td>
</tr>
</table>
<button type="button" id="ajax-save-btn" class="button button-primary">Save Text via AJAX</button>
<button type="button" id="ajax-test-btn" class="button button-secondary">Test AJAX</button>
<span id="ajax-status" style="margin-left: 10px;"></span>
</form>
<hr>
<script type="text/javascript">
jQuery(document).ready(function($){
// Test AJAX button
$('#ajax-test-btn').click(function(e){
e.preventDefault();
$('#ajax-status').html('<span style="color: blue;">Testing AJAX...</span>');
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'igny8_test_ajax'
},
success: function(response){
if (response.success) {
$('#ajax-status').html('<span style="color: green;">✓ ' + response.data + '</span>');
} else {
$('#ajax-status').html('<span style="color: red;">✗ Test failed: ' + response.data + '</span>');
}
},
error: function(){
$('#ajax-status').html('<span style="color: red;">✗ AJAX connection failed</span>');
}
});
});
// Save AJAX button
$('#ajax-save-btn').click(function(e){
e.preventDefault();
var textValue = $('#ajax_text_input').val().trim();
if (!textValue) {
$('#ajax-status').html('<span style="color: red;">Please enter some text</span>');
return;
}
// Show loading state
$('#ajax-status').html('<span style="color: blue;">Saving...</span>');
$('#ajax-save-btn').prop('disabled', true);
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'igny8_save_ajax_text',
ajax_text_input: textValue,
ajax_text_nonce: $('#ajax_text_nonce').val()
},
success: function(response){
if (response.success) {
$('#ajax-status').html('<span style="color: green;">✓ ' + response.data + '</span>');
// Update the input field to show the saved value
$('#ajax_text_input').val(textValue);
} else {
$('#ajax-status').html('<span style="color: red;">✗ Error: ' + response.data + '</span>');
}
},
error: function(xhr, status, error){
$('#ajax-status').html('<span style="color: red;">✗ AJAX Error: ' + error + '</span>');
},
complete: function(){
$('#ajax-save-btn').prop('disabled', false);
}
});
});
});
</script>
</div>
<?php
// Note: This file is included by help.php, so no need to capture content or include layout
// The content will be captured by the parent help.php file
?>

View File

@@ -0,0 +1,834 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : help.php
* @location : /modules/help/help.php
* @type : Admin Page
* @scope : Module Only
* @allowed : Help content, documentation, user guides, subpage routing
* @reusability : Single Use
* @notes : Main help page with subpage routing for help module
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Handle URL parameters for subpages
$subpage = $_GET['sp'] ?? 'help';
$GLOBALS['current_subpage'] = $subpage;
$GLOBALS['current_module'] = 'help';
// Start output buffering
ob_start();
switch ($subpage) {
case 'docs':
include plugin_dir_path(__FILE__) . 'docs.php';
break;
case 'system-testing':
include plugin_dir_path(__FILE__) . 'system-testing.php';
break;
case 'function-testing':
include plugin_dir_path(__FILE__) . 'function-testing.php';
break;
case 'help':
default:
// Main help content (existing content below)
?>
<div class="igny8-help-page">
<!-- Welcome Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Welcome to Igny8 AI SEO</h3>
<p class="igny8-card-subtitle">Your complete AI-powered SEO solution for WordPress</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-site igny8-dashboard-icon-lg igny8-dashboard-icon-blue"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<p>Igny8 is a comprehensive AI-powered SEO plugin that helps you research keywords, plan content, and optimize your website for search engines using artificial intelligence. Transform your content strategy with intelligent automation and AI-driven insights.</p>
<div class="igny8-feature-highlights">
<div class="igny8-feature-item">
<span class="dashicons dashicons-search"></span>
<strong>Smart Keyword Research</strong> - AI-powered keyword analysis and clustering
</div>
<div class="igny8-feature-item">
<span class="dashicons dashicons-edit"></span>
<strong>Content Generation</strong> - Create high-quality content with AI assistance
</div>
<div class="igny8-feature-item">
<span class="dashicons dashicons-chart-line"></span>
<strong>Performance Tracking</strong> - Monitor your SEO progress and results
</div>
</div>
</div>
</div>
<!-- Getting Started Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Getting Started</h3>
<p class="igny8-card-subtitle">Set up your AI-powered SEO workflow in minutes</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-arrow-right-alt igny8-dashboard-icon-lg igny8-dashboard-icon-green"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-module-overview">
<div class="igny8-module-section">
<div class="igny8-step-header">
<span class="igny8-step-number">1</span>
<h4>Configure AI Integration</h4>
</div>
<p>Go to <strong>Settings > AI Integration</strong> and enter your OpenAI API key. Choose your preferred AI model (GPT-4 recommended for best results).</p>
<ul>
<li><strong>API Key Setup:</strong> Enter your OpenAI API key for AI functionality</li>
<li><strong>Model Selection:</strong> Choose between GPT-4, GPT-3.5-turbo, or other available models</li>
<li><strong>Cost Management:</strong> Set daily limits to control API usage costs</li>
<li><strong>Testing:</strong> Test your AI integration to ensure everything works properly</li>
</ul>
<div class="igny8-step-tip">
<strong>💡 Tip:</strong> You can get an OpenAI API key from platform.openai.com
</div>
</div>
<div class="igny8-module-section">
<div class="igny8-step-header">
<span class="igny8-step-number">2</span>
<h4>Import Your Keywords</h4>
</div>
<p>Navigate to <strong>Planner > Keywords</strong> and import your keyword list or add keywords manually. Set search volume, difficulty, and intent for each keyword.</p>
<ul>
<li><strong>Bulk Import:</strong> Upload CSV files with keyword data</li>
<li><strong>Manual Entry:</strong> Add keywords one by one with detailed metrics</li>
<li><strong>Data Enrichment:</strong> Set search volume, difficulty, and CPC data</li>
<li><strong>Intent Classification:</strong> Categorize keywords by user intent</li>
</ul>
<div class="igny8-step-tip">
<strong>💡 Tip:</strong> Use the bulk import feature to add multiple keywords at once
</div>
</div>
<div class="igny8-module-section">
<div class="igny8-step-header">
<span class="igny8-step-number">3</span>
<h4>Create Content Clusters</h4>
</div>
<p>Go to <strong>Planner > Clusters</strong> and group related keywords into content topics. Use AI clustering to automatically organize your keywords.</p>
<ul>
<li><strong>AI Clustering:</strong> Automatically group related keywords using AI</li>
<li><strong>Manual Organization:</strong> Create custom clusters for specific topics</li>
<li><strong>Cluster Metrics:</strong> Track keyword count, volume, and difficulty</li>
<li><strong>Content Mapping:</strong> Link clusters to published content</li>
</ul>
<div class="igny8-step-tip">
<strong>💡 Tip:</strong> Let AI suggest cluster groupings for faster organization
</div>
</div>
<div class="igny8-module-section">
<div class="igny8-step-header">
<span class="igny8-step-number">4</span>
<h4>Generate Content Ideas</h4>
</div>
<p>Visit <strong>Planner > Ideas</strong> to generate AI-powered content ideas based on your clusters. Refine and prepare ideas for content creation.</p>
<ul>
<li><strong>AI Generation:</strong> Create content ideas using artificial intelligence</li>
<li><strong>Keyword Integration:</strong> Ideas include target keywords and topics</li>
<li><strong>Content Types:</strong> Generate ideas for blog posts, guides, and more</li>
<li><strong>Idea Management:</strong> Organize and prioritize content ideas</li>
</ul>
<div class="igny8-step-tip">
<strong>💡 Tip:</strong> Generate multiple ideas per cluster for content variety
</div>
</div>
<div class="igny8-module-section">
<div class="igny8-step-header">
<span class="igny8-step-number">5</span>
<h4>Create and Publish Content</h4>
</div>
<p>Go to <strong>Writer > Tasks</strong> to create content tasks from your ideas. Use AI to generate content or write manually, then publish directly to your site.</p>
<ul>
<li><strong>Task Creation:</strong> Convert ideas into actionable content tasks</li>
<li><strong>AI Content Generation:</strong> Generate high-quality content using AI</li>
<li><strong>Content Review:</strong> Edit and refine content before publishing</li>
<li><strong>Direct Publishing:</strong> Publish content directly to your WordPress site</li>
</ul>
<div class="igny8-step-tip">
<strong>💡 Tip:</strong> Review AI-generated content before publishing to ensure quality
</div>
</div>
</div>
</div>
</div>
<!-- Planner Module Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Planner Module</h3>
<p class="igny8-card-subtitle">Research keywords, create clusters, and generate content ideas</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-search igny8-dashboard-icon-lg igny8-dashboard-icon-purple"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-module-overview">
<div class="igny8-module-section">
<h4>Keywords Management</h4>
<p>Research and organize keywords by search volume, difficulty, and intent. Import keywords from various sources or add them manually.</p>
<ul>
<li><strong>Import Keywords:</strong> Upload CSV files or paste keyword lists</li>
<li><strong>Set Metrics:</strong> Add search volume, difficulty, and CPC data</li>
<li><strong>Intent Classification:</strong> Categorize keywords by user intent</li>
<li><strong>Status Tracking:</strong> Monitor keyword mapping and usage</li>
</ul>
</div>
<div class="igny8-module-section">
<h4>Content Clusters</h4>
<p>Group related keywords into content topics for better content planning and SEO strategy.</p>
<ul>
<li><strong>AI Clustering:</strong> Automatically group related keywords</li>
<li><strong>Manual Organization:</strong> Create custom clusters for specific topics</li>
<li><strong>Cluster Metrics:</strong> Track keyword count, volume, and difficulty</li>
<li><strong>Content Mapping:</strong> Link clusters to published content</li>
</ul>
</div>
<div class="igny8-module-section">
<h4>Content Ideas</h4>
<p>Generate AI-powered content ideas based on your keyword clusters and research.</p>
<ul>
<li><strong>AI Generation:</strong> Create content ideas using artificial intelligence</li>
<li><strong>Keyword Integration:</strong> Ideas include target keywords and topics</li>
<li><strong>Content Types:</strong> Generate ideas for blog posts, guides, and more</li>
<li><strong>Idea Management:</strong> Organize and prioritize content ideas</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Writer Module Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Writer Module</h3>
<p class="igny8-card-subtitle">Create, manage, and publish content with AI assistance</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-edit igny8-dashboard-icon-lg igny8-dashboard-icon-orange"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-module-overview">
<div class="igny8-module-section">
<h4>Content Tasks</h4>
<p>Create and manage content writing tasks with detailed specifications and deadlines.</p>
<ul>
<li><strong>Task Creation:</strong> Convert ideas into actionable content tasks</li>
<li><strong>Priority Setting:</strong> Organize tasks by importance and urgency</li>
<li><strong>Deadline Management:</strong> Set and track content deadlines</li>
<li><strong>Progress Tracking:</strong> Monitor task completion status</li>
</ul>
</div>
<div class="igny8-module-section">
<h4>AI Content Generation</h4>
<p>Generate high-quality content using AI based on your research and specifications.</p>
<ul>
<li><strong>Blog Posts:</strong> Create complete blog post content</li>
<li><strong>Landing Pages:</strong> Generate optimized landing page copy</li>
<li><strong>Product Descriptions:</strong> Write compelling product content</li>
<li><strong>SEO Meta:</strong> Generate titles, descriptions, and meta tags</li>
</ul>
</div>
<div class="igny8-module-section">
<h4>Content Workflow</h4>
<p>Track content from idea to publication with automated workflows and status updates.</p>
<ul>
<li><strong>Draft Management:</strong> Create and manage content drafts</li>
<li><strong>Review Process:</strong> Track content review and approval</li>
<li><strong>Publishing:</strong> Publish content directly to your WordPress site</li>
<li><strong>Status Updates:</strong> Automatic status updates throughout the workflow</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Analytics Module Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Analytics Module</h3>
<p class="igny8-card-subtitle">Track performance and monitor your SEO progress</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-chart-line igny8-dashboard-icon-lg igny8-dashboard-icon-blue"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-module-overview">
<div class="igny8-module-section">
<h4>Performance Metrics</h4>
<p>Monitor key SEO metrics and track your content performance over time.</p>
<ul>
<li><strong>Keyword Rankings:</strong> Track keyword position changes</li>
<li><strong>Content Performance:</strong> Monitor page views and engagement</li>
<li><strong>SEO Scores:</strong> Track overall SEO improvement</li>
<li><strong>Traffic Analysis:</strong> Monitor organic traffic growth</li>
</ul>
</div>
<div class="igny8-module-section">
<h4>Content Analytics</h4>
<p>Analyze your content performance and identify optimization opportunities.</p>
<ul>
<li><strong>Top Performing Content:</strong> Identify your best-performing pages</li>
<li><strong>Content Gaps:</strong> Find opportunities for new content</li>
<li><strong>Keyword Performance:</strong> Track which keywords drive traffic</li>
<li><strong>Conversion Tracking:</strong> Monitor content conversion rates</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Schedules Module Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Schedules Module</h3>
<p class="igny8-card-subtitle">Automate your SEO tasks with intelligent scheduling</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-clock igny8-dashboard-icon-lg igny8-dashboard-icon-green"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-module-overview">
<div class="igny8-module-section">
<h4>Automated Tasks</h4>
<p>Set up automated tasks to run keyword research, content generation, and optimization tasks.</p>
<ul>
<li><strong>Keyword Research:</strong> Automatically discover new keywords</li>
<li><strong>Content Generation:</strong> Schedule AI content creation</li>
<li><strong>SEO Audits:</strong> Regular automated SEO analysis</li>
<li><strong>Performance Reports:</strong> Scheduled performance reports</li>
</ul>
</div>
<div class="igny8-module-section">
<h4>Workflow Automation</h4>
<p>Create automated workflows that trigger based on specific conditions and schedules.</p>
<ul>
<li><strong>Trigger Conditions:</strong> Set up conditions for task execution</li>
<li><strong>Schedule Management:</strong> Configure when tasks should run</li>
<li><strong>Notification System:</strong> Get alerts when tasks complete</li>
<li><strong>Error Handling:</strong> Automatic retry and error management</li>
</ul>
</div>
</div>
</div>
</div>
<!-- AI Features Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>AI-Powered Features</h3>
<p class="igny8-card-subtitle">Leverage artificial intelligence for smarter SEO</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-robot igny8-dashboard-icon-lg igny8-dashboard-icon-purple"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-ai-features">
<div class="igny8-ai-feature">
<h4>Intelligent Keyword Analysis</h4>
<p>AI analyzes your keywords to determine search intent, difficulty, and optimization opportunities.</p>
<ul>
<li>Automatic intent classification (informational, commercial, navigational)</li>
<li>AI-powered difficulty scoring</li>
<li>Keyword clustering and grouping</li>
<li>Competition analysis and insights</li>
</ul>
</div>
<div class="igny8-ai-feature">
<h4>Smart Content Generation</h4>
<p>Generate high-quality, SEO-optimized content using advanced AI models.</p>
<ul>
<li>Context-aware content creation</li>
<li>SEO optimization built-in</li>
<li>Multiple content formats (blog posts, landing pages, product descriptions)</li>
<li>Automatic keyword integration</li>
</ul>
</div>
<div class="igny8-ai-feature">
<h4>Automated Content Optimization</h4>
<p>AI continuously optimizes your content for better search engine performance.</p>
<ul>
<li>Automatic SEO scoring and suggestions</li>
<li>Content improvement recommendations</li>
<li>Keyword density optimization</li>
<li>Readability enhancement</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Best Practices Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Best Practices</h3>
<p class="igny8-card-subtitle">Get the most out of your AI-powered SEO workflow</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-star-filled igny8-dashboard-icon-lg igny8-dashboard-icon-yellow"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-best-practices">
<div class="igny8-practice-category">
<h4>Content Strategy</h4>
<ul>
<li><strong>Plan Before You Write:</strong> Use the Planner module to research keywords and create clusters before writing</li>
<li><strong>Quality Over Quantity:</strong> Focus on creating high-quality, comprehensive content rather than many short posts</li>
<li><strong>Regular Content Updates:</strong> Keep your content fresh and updated to maintain search rankings</li>
<li><strong>User Intent Focus:</strong> Always consider what users are looking for when creating content</li>
</ul>
</div>
<div class="igny8-practice-category">
<h4>AI Usage</h4>
<ul>
<li><strong>Review AI Content:</strong> Always review and edit AI-generated content before publishing</li>
<li><strong>Use AI as a Starting Point:</strong> Let AI generate ideas and drafts, then add your unique perspective</li>
<li><strong>Monitor API Usage:</strong> Keep track of your OpenAI API usage to manage costs</li>
<li><strong>Test Different Prompts:</strong> Experiment with different AI prompts to get better results</li>
</ul>
</div>
<div class="igny8-practice-category">
<h4>SEO Optimization</h4>
<ul>
<li><strong>Keyword Research First:</strong> Always start with thorough keyword research</li>
<li><strong>Monitor Performance:</strong> Regularly check your analytics to see what's working</li>
<li><strong>Optimize for Users:</strong> Write for humans first, search engines second</li>
<li><strong>Build Authority:</strong> Focus on creating content that establishes your expertise</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Troubleshooting Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Troubleshooting</h3>
<p class="igny8-card-subtitle">Common issues and solutions</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-sos igny8-dashboard-icon-lg igny8-dashboard-icon-red"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-troubleshooting">
<div class="igny8-issue-category">
<h4>AI Integration Issues</h4>
<div class="igny8-issue">
<strong>Problem:</strong> AI features not working
<br><strong>Solution:</strong> Check your OpenAI API key in Settings > AI Integration. Ensure you have sufficient API credits and a stable internet connection.
</div>
<div class="igny8-issue">
<strong>Problem:</strong> Slow AI responses
<br><strong>Solution:</strong> Try switching to a faster model like GPT-3.5-turbo or check your internet connection speed.
</div>
</div>
<div class="igny8-issue-category">
<h4>Performance Issues</h4>
<div class="igny8-issue">
<strong>Problem:</strong> Slow page loading
<br><strong>Solution:</strong> Reduce the number of records per page in table settings, clear your browser cache, or check for plugin conflicts.
</div>
<div class="igny8-issue">
<strong>Problem:</strong> Missing data
<br><strong>Solution:</strong> Check that database tables are created correctly by visiting Settings > Status and running a system check.
</div>
</div>
<div class="igny8-issue-category">
<h4>Content Issues</h4>
<div class="igny8-issue">
<strong>Problem:</strong> AI content quality issues
<br><strong>Solution:</strong> Try different prompts, provide more specific instructions, or use the content as a starting point for manual editing.
</div>
<div class="igny8-issue">
<strong>Problem:</strong> Keywords not being used properly
<br><strong>Solution:</strong> Ensure keywords are properly imported and mapped to clusters before generating content.
</div>
</div>
</div>
</div>
</div>
<!-- Support Section -->
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Support & Resources</h3>
<p class="igny8-card-subtitle">Get help when you need it</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-info igny8-dashboard-icon-lg igny8-dashboard-icon-blue"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div class="igny8-support-resources">
<div class="igny8-support-item">
<h4>System Status</h4>
<p>Check your system health and configuration at <strong>Settings > Status</strong>. This page shows database status, AI integration, and system performance.</p>
</div>
<div class="igny8-support-item">
<h4>Debug Information</h4>
<p>Use the built-in debug tools to monitor real-time system status and identify any issues with your setup.</p>
</div>
<div class="igny8-support-item">
<h4>Regular Backups</h4>
<p>Always backup your WordPress site before making major changes or updates to ensure you can restore if needed.</p>
</div>
<div class="igny8-support-item">
<h4>API Monitoring</h4>
<p>Keep track of your OpenAI API usage to manage costs and ensure you don't exceed your limits.</p>
</div>
</div>
</div>
</div>
</div>
<?php
break;
}
?>
<style>
.igny8-help-page {
max-width: 1200px;
margin: 0 auto;
}
.igny8-standard-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.igny8-card-header-content {
display: flex;
align-items: center;
justify-content: space-between;
}
.igny8-card-title-text h3 {
margin: 0 0 8px 0;
font-size: 24px;
font-weight: 600;
}
.igny8-card-subtitle {
margin: 0;
font-size: 16px;
opacity: 0.9;
}
.igny8-dashboard-icon-lg {
font-size: 32px;
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
}
.igny8-dashboard-icon-blue { color: #3b82f6; }
.igny8-dashboard-icon-green { color: #10b981; }
.igny8-dashboard-icon-purple { color: #8b5cf6; }
.igny8-dashboard-icon-orange { color: #f59e0b; }
.igny8-dashboard-icon-yellow { color: #eab308; }
.igny8-dashboard-icon-red { color: #ef4444; }
.igny8-feature-highlights {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-top: 20px;
}
.igny8-feature-item {
display: flex;
align-items: center;
gap: 12px;
padding: 15px;
background: #f8fafc;
border-radius: 6px;
border-left: 4px solid #3b82f6;
}
.igny8-step-header {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 15px;
}
.igny8-step-header .igny8-step-number {
background: #3b82f6;
color: white;
width: 35px;
height: 35px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 16px;
flex-shrink: 0;
}
.igny8-step-header h4 {
margin: 0;
color: #1f2937;
font-size: 18px;
}
.igny8-step-tip {
background: #fef3c7;
border: 1px solid #f59e0b;
border-radius: 6px;
padding: 12px;
margin-top: 12px;
}
.igny8-module-overview {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
}
.igny8-module-section {
background: #f8fafc;
padding: 20px;
border-radius: 8px;
border-left: 4px solid #3b82f6;
}
.igny8-module-section h4 {
margin: 0 0 12px 0;
color: #1f2937;
font-size: 18px;
}
.igny8-module-section p {
margin: 0 0 15px 0;
color: #6b7280;
line-height: 1.6;
}
.igny8-module-section ul {
margin: 0;
padding-left: 20px;
}
.igny8-module-section li {
margin-bottom: 8px;
color: #374151;
line-height: 1.5;
}
.igny8-ai-features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 30px;
}
.igny8-ai-feature {
background: #f8fafc;
padding: 25px;
border-radius: 8px;
border-left: 4px solid #8b5cf6;
}
.igny8-ai-feature h4 {
margin: 0 0 12px 0;
color: #1f2937;
font-size: 18px;
}
.igny8-ai-feature p {
margin: 0 0 15px 0;
color: #6b7280;
line-height: 1.6;
}
.igny8-best-practices {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
}
.igny8-practice-category {
background: #f8fafc;
padding: 20px;
border-radius: 8px;
border-left: 4px solid #10b981;
}
.igny8-practice-category h4 {
margin: 0 0 15px 0;
color: #1f2937;
font-size: 18px;
}
.igny8-troubleshooting {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 30px;
}
.igny8-issue-category {
background: #f8fafc;
padding: 20px;
border-radius: 8px;
border-left: 4px solid #ef4444;
}
.igny8-issue-category h4 {
margin: 0 0 15px 0;
color: #1f2937;
font-size: 18px;
}
.igny8-issue {
background: white;
padding: 15px;
border-radius: 6px;
margin-bottom: 15px;
border-left: 3px solid #f59e0b;
}
.igny8-support-resources {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
.igny8-support-item {
background: #f8fafc;
padding: 20px;
border-radius: 8px;
border-left: 4px solid #3b82f6;
}
.igny8-support-item h4 {
margin: 0 0 10px 0;
color: #1f2937;
font-size: 16px;
}
.igny8-support-item p {
margin: 0;
color: #6b7280;
line-height: 1.6;
}
@media (max-width: 768px) {
.igny8-card-header-content {
flex-direction: column;
text-align: center;
gap: 15px;
}
.igny8-step-header {
flex-direction: column;
text-align: center;
gap: 10px;
}
.igny8-module-overview,
.igny8-ai-features,
.igny8-best-practices,
.igny8-troubleshooting {
grid-template-columns: 1fr;
}
}
</style>
<?php
// Capture page content
$igny8_page_content = ob_get_clean();
// Include global layout
include plugin_dir_path(__FILE__) . '../../core/global-layout.php';
?>

View File

@@ -0,0 +1,329 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : system-testing.php
* @location : /modules/help/system-testing.php
* @type : Admin Page
* @scope : Module Only
* @allowed : System testing, functionality verification, debugging tools
* @reusability : Single Use
* @notes : System testing interface for help module
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Dev-only access guard
if (!defined('IGNY8_DEV') || IGNY8_DEV !== true) {
return;
}
?>
<div class="notice notice-info">
<p><strong>Testing Interface:</strong> Basic functionality verification for taxonomy, schema, and UI components.</p>
</div>
<div class="igny8-test-container" style="display: flex; gap: 20px; margin-top: 20px;">
<!-- Schema Tests -->
<div class="igny8-test-section" style="flex: 1; border: 1px solid #ddd; padding: 20px; border-radius: 5px;">
<h2>Database Schema Tests</h2>
<div class="test-buttons">
<button class="button button-primary" onclick="testDatabaseConnection()">Test DB Connection</button>
<button class="button" onclick="testTableExists()">Check Tables</button>
<button class="button" onclick="testSchemaIntegrity()">Schema Integrity</button>
</div>
<div id="schema-results" class="test-results" style="margin-top: 15px; padding: 10px; background: #f9f9f9; border-radius: 3px; min-height: 100px;">
<p><em>Click a test button to see results...</em></p>
</div>
</div>
<!-- Taxonomy Tests -->
<div class="igny8-test-section" style="flex: 1; border: 1px solid #ddd; padding: 20px; border-radius: 5px;">
<h2>Taxonomy Tests</h2>
<div class="test-buttons">
<button class="button button-primary" onclick="testTaxonomyRegistration()">Test Taxonomy</button>
<button class="button" onclick="testCreateTerm()">Create Test Term</button>
<button class="button" onclick="testTermQueries()">Query Terms</button>
</div>
<div id="taxonomy-results" class="test-results" style="margin-top: 15px; padding: 10px; background: #f9f9f9; border-radius: 3px; min-height: 100px;">
<p><em>Click a test button to see results...</em></p>
</div>
</div>
<!-- AJAX Tests -->
<div class="igny8-test-section" style="flex: 1; border: 1px solid #ddd; padding: 20px; border-radius: 5px;">
<h2>AJAX & API Tests</h2>
<div class="test-buttons">
<button class="button button-primary" onclick="testAjaxConnection()">Test AJAX</button>
<button class="button" onclick="testTableData()">Load Table Data</button>
<button class="button" onclick="testWorkflowTriggers()">Test Workflows</button>
</div>
<div id="ajax-results" class="test-results" style="margin-top: 15px; padding: 10px; background: #f9f9f9; border-radius: 3px; min-height: 100px;">
<p><em>Click a test button to see results...</em></p>
</div>
</div>
</div>
<!-- Quick Record Test -->
<div style="margin-top: 30px; border: 1px solid #ddd; padding: 20px; border-radius: 5px;">
<h2>Quick Record Test</h2>
<p>Test basic record operations:</p>
<div style="display: flex; gap: 10px; align-items: center; margin-top: 10px;">
<input type="text" id="test-record-name" placeholder="Test record name" value="Test Record <?php echo date('Y-m-d H:i:s'); ?>" style="flex: 1;">
<button class="button button-primary" onclick="createTestRecord()">Create Test Record</button>
<button class="button" onclick="listTestRecords()">List Records</button>
<button class="button" onclick="clearTestRecords()" style="color: #d63638;">Clear All</button>
</div>
<div id="record-results" class="test-results" style="margin-top: 15px; padding: 10px; background: #f9f9f9; border-radius: 3px; min-height: 50px;">
<p><em>Create or list records to see results...</em></p>
</div>
</div>
<script>
// Test functions
function testDatabaseConnection() {
updateResults('schema-results', 'Testing database connection...', 'info');
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=igny8_test_db_connection&nonce=' + igny8_ajax.nonce
})
.then(response => response.json())
.then(data => {
updateResults('schema-results', JSON.stringify(data, null, 2), data.success ? 'success' : 'error');
})
.catch(error => {
updateResults('schema-results', 'Error: ' + error.message, 'error');
});
}
function testTableExists() {
updateResults('schema-results', 'Checking table existence...', 'info');
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=igny8_test_table_exists&nonce=' + igny8_ajax.nonce
})
.then(response => response.json())
.then(data => {
updateResults('schema-results', JSON.stringify(data, null, 2), data.success ? 'success' : 'error');
})
.catch(error => {
updateResults('schema-results', 'Error: ' + error.message, 'error');
});
}
function testSchemaIntegrity() {
updateResults('schema-results', 'Testing schema integrity...', 'info');
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=igny8_test_schema_integrity&nonce=' + igny8_ajax.nonce
})
.then(response => response.json())
.then(data => {
updateResults('schema-results', JSON.stringify(data, null, 2), data.success ? 'success' : 'error');
})
.catch(error => {
updateResults('schema-results', 'Error: ' + error.message, 'error');
});
}
function testTaxonomyRegistration() {
updateResults('taxonomy-results', 'Testing taxonomy registration...', 'info');
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=igny8_test_taxonomy&nonce=' + igny8_ajax.nonce
})
.then(response => response.json())
.then(data => {
updateResults('taxonomy-results', JSON.stringify(data, null, 2), data.success ? 'success' : 'error');
})
.catch(error => {
updateResults('taxonomy-results', 'Error: ' + error.message, 'error');
});
}
function testCreateTerm() {
updateResults('taxonomy-results', 'Creating test term...', 'info');
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=igny8_test_create_term&nonce=' + igny8_ajax.nonce
})
.then(response => response.json())
.then(data => {
updateResults('taxonomy-results', JSON.stringify(data, null, 2), data.success ? 'success' : 'error');
})
.catch(error => {
updateResults('taxonomy-results', 'Error: ' + error.message, 'error');
});
}
function testTermQueries() {
updateResults('taxonomy-results', 'Querying terms...', 'info');
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=igny8_test_term_queries&nonce=' + igny8_ajax.nonce
})
.then(response => response.json())
.then(data => {
updateResults('taxonomy-results', JSON.stringify(data, null, 2), data.success ? 'success' : 'error');
})
.catch(error => {
updateResults('taxonomy-results', 'Error: ' + error.message, 'error');
});
}
function testAjaxConnection() {
updateResults('ajax-results', 'Testing AJAX connection...', 'info');
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=igny8_test_ajax&nonce=' + igny8_ajax.nonce
})
.then(response => response.json())
.then(data => {
updateResults('ajax-results', JSON.stringify(data, null, 2), data.success ? 'success' : 'error');
})
.catch(error => {
updateResults('ajax-results', 'Error: ' + error.message, 'error');
});
}
function testTableData() {
updateResults('ajax-results', 'Testing table data loading...', 'info');
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=igny8_get_table_data&nonce=' + igny8_ajax.nonce + '&table=planner_keywords&page=1&per_page=5'
})
.then(response => response.json())
.then(data => {
updateResults('ajax-results', JSON.stringify(data, null, 2), data.success ? 'success' : 'error');
})
.catch(error => {
updateResults('ajax-results', 'Error: ' + error.message, 'error');
});
}
function testWorkflowTriggers() {
updateResults('ajax-results', 'Testing workflow triggers...', 'info');
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=igny8_test_workflows&nonce=' + igny8_ajax.nonce
})
.then(response => response.json())
.then(data => {
updateResults('ajax-results', JSON.stringify(data, null, 2), data.success ? 'success' : 'error');
})
.catch(error => {
updateResults('ajax-results', 'Error: ' + error.message, 'error');
});
}
function createTestRecord() {
const name = document.getElementById('test-record-name').value;
if (!name.trim()) {
updateResults('record-results', 'Please enter a record name', 'error');
return;
}
updateResults('record-results', 'Creating test record...', 'info');
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=igny8_test_create_record&nonce=' + igny8_ajax.nonce + '&name=' + encodeURIComponent(name)
})
.then(response => response.json())
.then(data => {
updateResults('record-results', JSON.stringify(data, null, 2), data.success ? 'success' : 'error');
})
.catch(error => {
updateResults('record-results', 'Error: ' + error.message, 'error');
});
}
function listTestRecords() {
updateResults('record-results', 'Listing test records...', 'info');
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=igny8_test_list_records&nonce=' + igny8_ajax.nonce
})
.then(response => response.json())
.then(data => {
updateResults('record-results', JSON.stringify(data, null, 2), data.success ? 'success' : 'error');
})
.catch(error => {
updateResults('record-results', 'Error: ' + error.message, 'error');
});
}
function clearTestRecords() {
if (!confirm('Are you sure you want to clear all test records?')) return;
updateResults('record-results', 'Clearing test records...', 'info');
fetch(ajaxurl, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'action=igny8_test_clear_records&nonce=' + igny8_ajax.nonce
})
.then(response => response.json())
.then(data => {
updateResults('record-results', JSON.stringify(data, null, 2), data.success ? 'success' : 'error');
})
.catch(error => {
updateResults('record-results', 'Error: ' + error.message, 'error');
});
}
function updateResults(elementId, content, type) {
const element = document.getElementById(elementId);
const timestamp = new Date().toLocaleTimeString();
const prefix = type === 'success' ? '✅' : type === 'error' ? '❌' : '';
element.innerHTML = `<div style="margin-bottom: 10px;"><strong>${prefix} [${timestamp}]</strong></div><pre style="white-space: pre-wrap; font-size: 12px;">${content}</pre>`;
if (type === 'success') {
element.style.borderLeft = '4px solid #00a32a';
} else if (type === 'error') {
element.style.borderLeft = '4px solid #d63638';
} else {
element.style.borderLeft = '4px solid #0073aa';
}
}
</script>
<?php
// Note: This file is included by help.php, so no need to capture content or include layout
// The content will be captured by the parent help.php file
?>

View File

@@ -0,0 +1,231 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : home.php
* @location : modules/
* @type : Home Page Template
* @scope : Global
* @allowed : Home page content only, no routing or business logic
* @reusability : Global
* @notes : Home page dashboard content
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Get workflow data
$workflow_data = igny8_get_system_workflow_data();
// Start output buffering
ob_start();
?>
<div class="igny8-module-home">
<!-- System-Wide AI Workflow Guide -->
<div class="igny8-system-workflow">
<div class="igny8-system-workflow-header">
<span class="dashicons dashicons-networking"></span>
<div>
<h2>Complete AI Content Workflow</h2>
<p class="igny8-system-workflow-subtitle">Track your progress through the entire content creation and optimization pipeline</p>
</div>
</div>
<div class="igny8-system-steps-container">
<!-- Step 1: Add Keywords -->
<div class="igny8-system-step <?php echo $workflow_data['keywords']['status']; ?> <?php echo !$workflow_data['keywords']['module_enabled'] ? 'disabled' : ''; ?>"
<?php if ($workflow_data['keywords']['url']): ?>onclick="window.location.href='<?php echo admin_url($workflow_data['keywords']['url']); ?>'"<?php endif; ?>>
<div class="igny8-system-step-number">1</div>
<div class="igny8-system-step-title">Add Keywords</div>
<div class="igny8-system-step-status">
<span class="igny8-system-step-status-icon"><?php echo $workflow_data['keywords']['status'] === 'completed' ? '✅' : '⚠'; ?></span>
<span class="igny8-system-step-status-text"><?php echo $workflow_data['keywords']['status'] === 'completed' ? 'Completed' : 'Missing'; ?></span>
</div>
<div class="igny8-system-step-data">
<?php if ($workflow_data['keywords']['count'] > 0): ?>
<?php echo $workflow_data['keywords']['count']; ?> keywords added
<?php else: ?>
No keywords yet
<?php endif; ?>
</div>
<?php if ($workflow_data['keywords']['status'] === 'missing' && $workflow_data['keywords']['module_enabled']): ?>
<div class="igny8-system-step-action">
<a href="<?php echo admin_url($workflow_data['keywords']['url']); ?>" class="igny8-btn igny8-btn-primary">Go to Planner</a>
</div>
<?php endif; ?>
<div class="igny8-system-step-connector"></div>
</div>
<!-- Step 2: Select Sector -->
<div class="igny8-system-step <?php echo $workflow_data['sector']['status']; ?> <?php echo !$workflow_data['sector']['module_enabled'] ? 'disabled' : ''; ?>"
<?php if ($workflow_data['sector']['url']): ?>onclick="window.location.href='<?php echo admin_url($workflow_data['sector']['url']); ?>'"<?php endif; ?>>
<div class="igny8-system-step-number">2</div>
<div class="igny8-system-step-title">Select Sector</div>
<div class="igny8-system-step-status">
<span class="igny8-system-step-status-icon"><?php echo $workflow_data['sector']['status'] === 'completed' ? '✅' : '⚠'; ?></span>
<span class="igny8-system-step-status-text"><?php echo $workflow_data['sector']['status'] === 'completed' ? 'Selected' : 'Required'; ?></span>
</div>
<div class="igny8-system-step-data">
<?php if ($workflow_data['sector']['selected']): ?>
Sector configured
<?php else: ?>
Required for AI workflows
<?php endif; ?>
</div>
<?php if ($workflow_data['sector']['status'] === 'missing' && $workflow_data['sector']['module_enabled']): ?>
<div class="igny8-system-step-action">
<a href="<?php echo admin_url($workflow_data['sector']['url']); ?>" class="igny8-btn igny8-btn-primary">Configure</a>
</div>
<?php endif; ?>
<div class="igny8-system-step-connector"></div>
</div>
<!-- Step 3: Auto Cluster -->
<div class="igny8-system-step <?php echo $workflow_data['clusters']['status']; ?> <?php echo !$workflow_data['clusters']['module_enabled'] ? 'disabled' : ''; ?>"
<?php if ($workflow_data['clusters']['url']): ?>onclick="window.location.href='<?php echo admin_url($workflow_data['clusters']['url']); ?>'"<?php endif; ?>>
<div class="igny8-system-step-number">3</div>
<div class="igny8-system-step-title">Auto Cluster</div>
<div class="igny8-system-step-status">
<span class="igny8-system-step-status-icon"><?php echo $workflow_data['clusters']['status'] === 'completed' ? '✅' : ($workflow_data['clusters']['status'] === 'in_progress' ? '⏳' : '⚠'); ?></span>
<span class="igny8-system-step-status-text"><?php echo $workflow_data['clusters']['status'] === 'completed' ? 'Completed' : ($workflow_data['clusters']['status'] === 'in_progress' ? 'In Progress' : 'Missing'); ?></span>
</div>
<div class="igny8-system-step-data">
<?php if ($workflow_data['clusters']['unmapped_keywords'] > 0): ?>
<?php echo $workflow_data['clusters']['unmapped_keywords']; ?> unmapped keywords
<?php elseif ($workflow_data['clusters']['count'] > 0): ?>
<?php echo $workflow_data['clusters']['count']; ?> clusters created
<?php else: ?>
No clusters yet
<?php endif; ?>
</div>
<?php if ($workflow_data['clusters']['status'] === 'in_progress' && $workflow_data['clusters']['module_enabled']): ?>
<div class="igny8-system-step-action">
<a href="<?php echo admin_url($workflow_data['clusters']['url']); ?>" class="igny8-btn igny8-btn-primary">Start Clustering</a>
</div>
<?php endif; ?>
<div class="igny8-system-step-connector"></div>
</div>
<!-- Step 4: Generate Ideas -->
<div class="igny8-system-step <?php echo $workflow_data['ideas']['status']; ?> <?php echo !$workflow_data['ideas']['module_enabled'] ? 'disabled' : ''; ?>"
<?php if ($workflow_data['ideas']['url']): ?>onclick="window.location.href='<?php echo admin_url($workflow_data['ideas']['url']); ?>'"<?php endif; ?>>
<div class="igny8-system-step-number">4</div>
<div class="igny8-system-step-title">Generate Ideas</div>
<div class="igny8-system-step-status">
<span class="igny8-system-step-status-icon"><?php echo $workflow_data['ideas']['status'] === 'completed' ? '✅' : '⚠'; ?></span>
<span class="igny8-system-step-status-text"><?php echo $workflow_data['ideas']['status'] === 'completed' ? 'Completed' : 'Missing'; ?></span>
</div>
<div class="igny8-system-step-data">
<?php if ($workflow_data['ideas']['count'] > 0): ?>
<?php echo $workflow_data['ideas']['count']; ?> ideas generated
<?php else: ?>
No ideas yet
<?php endif; ?>
</div>
<?php if ($workflow_data['ideas']['status'] === 'missing' && $workflow_data['ideas']['module_enabled']): ?>
<div class="igny8-system-step-action">
<a href="<?php echo admin_url($workflow_data['ideas']['url']); ?>" class="igny8-btn igny8-btn-primary">Generate Ideas</a>
</div>
<?php endif; ?>
<div class="igny8-system-step-connector"></div>
</div>
<!-- Step 5: Queue to Writer -->
<div class="igny8-system-step <?php echo $workflow_data['queue']['status']; ?> <?php echo !$workflow_data['queue']['module_enabled'] ? 'disabled' : ''; ?>"
<?php if ($workflow_data['queue']['url']): ?>onclick="window.location.href='<?php echo admin_url($workflow_data['queue']['url']); ?>'"<?php endif; ?>>
<div class="igny8-system-step-number">5</div>
<div class="igny8-system-step-title">Queue to Writer</div>
<div class="igny8-system-step-status">
<span class="igny8-system-step-status-icon"><?php echo $workflow_data['queue']['status'] === 'completed' ? '✅' : ($workflow_data['queue']['status'] === 'in_progress' ? '⏳' : '⚠'); ?></span>
<span class="igny8-system-step-status-text"><?php echo $workflow_data['queue']['status'] === 'completed' ? 'Completed' : ($workflow_data['queue']['status'] === 'in_progress' ? 'In Progress' : 'Missing'); ?></span>
</div>
<div class="igny8-system-step-data">
<?php if ($workflow_data['queue']['queued_ideas'] > 0): ?>
<?php echo $workflow_data['queue']['queued_ideas']; ?> ideas ready to queue
<?php else: ?>
All ideas queued
<?php endif; ?>
</div>
<?php if ($workflow_data['queue']['status'] === 'in_progress' && $workflow_data['queue']['module_enabled']): ?>
<div class="igny8-system-step-action">
<a href="<?php echo admin_url($workflow_data['queue']['url']); ?>" class="igny8-btn igny8-btn-primary">Queue Ideas</a>
</div>
<?php endif; ?>
<div class="igny8-system-step-connector"></div>
</div>
<!-- Step 6: Generate Drafts -->
<div class="igny8-system-step <?php echo $workflow_data['drafts']['status']; ?> <?php echo !$workflow_data['drafts']['module_enabled'] ? 'disabled' : ''; ?>"
<?php if ($workflow_data['drafts']['url']): ?>onclick="window.location.href='<?php echo admin_url($workflow_data['drafts']['url']); ?>'"<?php endif; ?>>
<div class="igny8-system-step-number">6</div>
<div class="igny8-system-step-title">Generate Drafts</div>
<div class="igny8-system-step-status">
<span class="igny8-system-step-status-icon"><?php echo $workflow_data['drafts']['status'] === 'completed' ? '✅' : ($workflow_data['drafts']['status'] === 'in_progress' ? '⏳' : '⚠'); ?></span>
<span class="igny8-system-step-status-text"><?php echo $workflow_data['drafts']['status'] === 'completed' ? 'Completed' : ($workflow_data['drafts']['status'] === 'in_progress' ? 'In Progress' : 'Missing'); ?></span>
</div>
<div class="igny8-system-step-data">
<?php if ($workflow_data['drafts']['queued_tasks'] > 0): ?>
<?php echo $workflow_data['drafts']['queued_tasks']; ?> tasks ready for AI
<?php elseif ($workflow_data['drafts']['draft_tasks'] > 0): ?>
<?php echo $workflow_data['drafts']['draft_tasks']; ?> drafts generated
<?php else: ?>
No drafts yet
<?php endif; ?>
</div>
<?php if ($workflow_data['drafts']['status'] === 'in_progress' && $workflow_data['drafts']['module_enabled']): ?>
<div class="igny8-system-step-action">
<a href="<?php echo admin_url($workflow_data['drafts']['url']); ?>" class="igny8-btn igny8-btn-primary">Go to Writer</a>
</div>
<?php endif; ?>
<div class="igny8-system-step-connector"></div>
</div>
<!-- Step 7: Publish Content -->
<div class="igny8-system-step <?php echo $workflow_data['publish']['status']; ?> <?php echo !$workflow_data['publish']['module_enabled'] ? 'disabled' : ''; ?>"
<?php if ($workflow_data['publish']['url']): ?>onclick="window.location.href='<?php echo admin_url($workflow_data['publish']['url']); ?>'"<?php endif; ?>>
<div class="igny8-system-step-number">7</div>
<div class="igny8-system-step-title">Publish Content</div>
<div class="igny8-system-step-status">
<span class="igny8-system-step-status-icon"><?php echo $workflow_data['publish']['status'] === 'completed' ? '✅' : ($workflow_data['publish']['status'] === 'in_progress' ? '⏳' : '⚠'); ?></span>
<span class="igny8-system-step-status-text"><?php echo $workflow_data['publish']['status'] === 'completed' ? 'Completed' : ($workflow_data['publish']['status'] === 'in_progress' ? 'In Progress' : 'Missing'); ?></span>
</div>
<div class="igny8-system-step-data">
<?php if ($workflow_data['publish']['published_tasks'] > 0): ?>
<?php echo $workflow_data['publish']['published_tasks']; ?> content published
<?php elseif ($workflow_data['publish']['draft_tasks'] > 0): ?>
<?php echo $workflow_data['publish']['draft_tasks']; ?> drafts ready to publish
<?php else: ?>
No content to publish
<?php endif; ?>
</div>
<?php if ($workflow_data['publish']['status'] === 'in_progress' && $workflow_data['publish']['module_enabled']): ?>
<div class="igny8-system-step-action">
<a href="<?php echo admin_url($workflow_data['publish']['url']); ?>" class="igny8-btn igny8-btn-primary">Publish Now</a>
</div>
<?php endif; ?>
<div class="igny8-system-step-connector"></div>
</div>
</div>
</div>
<!-- Welcome Section -->
<div class="igny8-card">
<div class="igny8-card-header">
<h3>Welcome to Igny8 AI SEO OS</h3>
</div>
<div class="igny8-card-body">
<p>Your comprehensive SEO management platform. Use the workflow guide above to track your progress through the complete content creation and optimization pipeline.</p>
<p>Each step shows your current status and provides direct links to the relevant modules. Click on any step to navigate to the appropriate section.</p>
</div>
</div>
</div>
<?php
// Capture page content
$igny8_page_content = ob_get_clean();
// Include global layout
include_once plugin_dir_path(__FILE__) . '../core/global-layout.php';
?>

View File

@@ -0,0 +1,84 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : clusters.php
* @location : modules/planner/
* @type : Clusters Management Page
* @scope : Planner Module
* @allowed : Keyword clustering, content grouping, and cluster management
* @reusability : Planner Module
* @notes : Configuration-driven clusters management interface using complete component system
*
* @package Igny8Compact
* @since 1.0.0
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Set module variables
$GLOBALS['current_submodule'] = 'clusters';
$GLOBALS['current_module'] = 'planner';
// Load all required components
require_once plugin_dir_path(__FILE__) . '../../modules/components/filters-tpl.php';
require_once plugin_dir_path(__FILE__) . '../../modules/components/actions-tpl.php';
require_once plugin_dir_path(__FILE__) . '../../modules/components/table-tpl.php';
require_once plugin_dir_path(__FILE__) . '../../modules/components/pagination-tpl.php';
// Set table ID for components
$table_id = 'planner_clusters';
?>
<div class="igny8-module-page">
<div class="igny8-container">
<!-- Clusters Management Section -->
<div class="igny8-dashboard-section">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Keyword Clusters</h3>
<p class="igny8-card-subtitle">Organize keywords into content clusters for better SEO strategy</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-networking igny8-dashboard-icon-lg igny8-dashboard-icon-green"></span>
</div>
</div>
</div>
<!-- Complete Submodule Interface -->
<?php
// Render all components in correct order
echo igny8_render_filters($table_id);
echo igny8_render_table_actions($table_id);
echo igny8_render_table($table_id);
echo igny8_render_pagination($table_id);
// Get cluster options for dropdowns
$cluster_options = igny8_get_cluster_options();
// Load filters configuration for JavaScript
$filters_config = require plugin_dir_path(__FILE__) . '../../modules/config/filters-config.php';
// Localize script for table initialization
wp_localize_script('igny8-admin-js', 'IGNY8_PAGE', [
'tableId' => $table_id,
'module' => 'planner',
'submodule' => 'clusters',
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('igny8_planner_settings'),
'defaultPerPage' => get_option('igny8_records_per_page', 20),
'clusterOptions' => $cluster_options,
'filtersConfig' => $filters_config,
'cronKey' => igny8_needs_cron_functionality() ? igny8_get_or_generate_cron_key() : null
]);
?>
</div>
</div>
</div>

View File

@@ -0,0 +1,84 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : ideas.php
* @location : modules/planner/
* @type : Ideas Management Page
* @scope : Planner Module
* @allowed : Content ideas generation, management, and organization
* @reusability : Planner Module
* @notes : Configuration-driven ideas management interface using complete component system
*
* @package Igny8Compact
* @since 1.0.0
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Set module variables
$GLOBALS['current_submodule'] = 'ideas';
$GLOBALS['current_module'] = 'planner';
// Load all required components
require_once plugin_dir_path(__FILE__) . '../../modules/components/filters-tpl.php';
require_once plugin_dir_path(__FILE__) . '../../modules/components/actions-tpl.php';
require_once plugin_dir_path(__FILE__) . '../../modules/components/table-tpl.php';
require_once plugin_dir_path(__FILE__) . '../../modules/components/pagination-tpl.php';
// Set table ID for components
$table_id = 'planner_ideas';
?>
<div class="igny8-module-page">
<div class="igny8-container">
<!-- Ideas Management Section -->
<div class="igny8-dashboard-section">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Content Ideas</h3>
<p class="igny8-card-subtitle">Generate and manage content ideas for your clusters</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-lightbulb igny8-dashboard-icon-lg igny8-dashboard-icon-purple"></span>
</div>
</div>
</div>
<!-- Complete Submodule Interface -->
<?php
// Render all components in correct order
echo igny8_render_filters($table_id);
echo igny8_render_table_actions($table_id);
echo igny8_render_table($table_id);
echo igny8_render_pagination($table_id);
// Get cluster options for dropdowns
$cluster_options = igny8_get_cluster_options();
// Load filters configuration for JavaScript
$filters_config = require plugin_dir_path(__FILE__) . '../../modules/config/filters-config.php';
// Localize script for table initialization
wp_localize_script('igny8-admin-js', 'IGNY8_PAGE', [
'tableId' => $table_id,
'module' => 'planner',
'submodule' => 'ideas',
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('igny8_planner_settings'),
'defaultPerPage' => get_option('igny8_records_per_page', 20),
'clusterOptions' => $cluster_options,
'filtersConfig' => $filters_config,
'cronKey' => igny8_needs_cron_functionality() ? igny8_get_or_generate_cron_key() : null
]);
?>
</div>
</div>
</div>

View File

@@ -0,0 +1,84 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : keywords.php
* @location : modules/planner/
* @type : Keywords Management Page
* @scope : Planner Module
* @allowed : Keywords research, import, analysis, and management
* @reusability : Planner Module
* @notes : Configuration-driven keywords management interface using complete component system
*
* @package Igny8Compact
* @since 1.0.0
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Set module variables
$GLOBALS['current_submodule'] = 'keywords';
$GLOBALS['current_module'] = 'planner';
// Load all required components
require_once plugin_dir_path(__FILE__) . '../../modules/components/filters-tpl.php';
require_once plugin_dir_path(__FILE__) . '../../modules/components/actions-tpl.php';
require_once plugin_dir_path(__FILE__) . '../../modules/components/table-tpl.php';
require_once plugin_dir_path(__FILE__) . '../../modules/components/pagination-tpl.php';
// Set table ID for components
$table_id = 'planner_keywords';
?>
<div class="igny8-module-page">
<div class="igny8-container">
<!-- Keywords Management Section -->
<div class="igny8-dashboard-section">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Keywords Management</h3>
<p class="igny8-card-subtitle">Import, analyze, and organize your keywords</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-tag igny8-dashboard-icon-lg igny8-dashboard-icon-blue"></span>
</div>
</div>
</div>
<!-- Complete Submodule Interface -->
<?php
// Render all components in correct order
echo igny8_render_filters($table_id);
echo igny8_render_table_actions($table_id);
echo igny8_render_table($table_id);
echo igny8_render_pagination($table_id);
// Get cluster options for dropdowns
$cluster_options = igny8_get_cluster_options();
// Load filters configuration for JavaScript
$filters_config = require plugin_dir_path(__FILE__) . '../../modules/config/filters-config.php';
// Localize script for table initialization
wp_localize_script('igny8-admin-js', 'IGNY8_PAGE', [
'tableId' => $table_id,
'module' => 'planner',
'submodule' => 'keywords',
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('igny8_planner_settings'),
'defaultPerPage' => get_option('igny8_records_per_page', 20),
'clusterOptions' => $cluster_options,
'filtersConfig' => $filters_config,
'cronKey' => igny8_needs_cron_functionality() ? igny8_get_or_generate_cron_key() : null
]);
?>
</div>
</div>
</div>

View File

@@ -0,0 +1,653 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : planner.php
* @location : /modules/planner/planner.php
* @type : Admin Page
* @scope : Module Only
* @allowed : Planner module logic, subpage routing, step data functions
* @reusability : Single Use
* @notes : Main planner page with subpage routing and step data functions
*/
if (!defined('ABSPATH')) exit;
// Component render functions are loaded centrally via global-layout.php
/**
* Get step data for Planner workflow guide
*
* @return array Array of step data with status and counts
*/
function igny8_get_planner_step_data() {
global $wpdb;
// Get counts for each step
$keywords_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_keywords");
$unmapped_keywords = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_keywords WHERE cluster_id IS NULL OR cluster_id = 0");
$clusters_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_clusters");
$ideas_count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_content_ideas");
$queued_ideas = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}igny8_content_ideas WHERE status = 'new'");
// Check sector selection
$sector_selected = !empty(igny8_get_saved_sector_selection());
return [
'keywords' => [
'count' => $keywords_count,
'unmapped' => $unmapped_keywords,
'status' => $keywords_count > 0 ? 'completed' : 'pending'
],
'sector' => [
'selected' => $sector_selected,
'status' => $sector_selected ? 'completed' : 'current'
],
'clusters' => [
'count' => $clusters_count,
'unmapped_keywords' => $unmapped_keywords,
'status' => $unmapped_keywords == 0 && $clusters_count > 0 ? 'completed' : ($unmapped_keywords > 0 ? 'current' : 'pending')
],
'ideas' => [
'count' => $ideas_count,
'status' => $ideas_count > 0 ? 'completed' : 'pending'
],
'queue' => [
'queued_ideas' => $queued_ideas,
'status' => $queued_ideas == 0 && $ideas_count > 0 ? 'completed' : ($queued_ideas > 0 ? 'current' : 'pending')
]
];
}
// Handle URL parameters for subpages
$subpage = $_GET['sm'] ?? 'home';
$GLOBALS['current_submodule'] = $subpage;
$GLOBALS['current_module'] = 'planner';
// Start output buffering
ob_start();
switch ($subpage) {
case 'keywords':
include plugin_dir_path(__FILE__) . 'keywords.php';
break;
case 'clusters':
include plugin_dir_path(__FILE__) . 'clusters.php';
break;
case 'ideas':
include plugin_dir_path(__FILE__) . 'ideas.php';
break;
case 'home':
default:
// Home dashboard content
?>
<div class="igny8-module-home">
<?php
// Check if sector is selected - show alert if not (reuse existing function)
$sector_options = igny8_get_sector_options();
if (empty($sector_options)):
?>
<!-- Sector Selection Required Alert -->
<div class="igny8-card igny8-card-warning" style="margin-bottom: 20px; border-left: 4px solid var(--amber); background: linear-gradient(135deg, #fff8f0 0%, #fef3e7 100%);">
<div class="igny8-card-header">
<span style="font-size: 20px; margin-right: 12px;">⚠</span>
<strong>Please select a Sector to continue.</strong> Sector selection is required to start AI-based workflows.
</div>
</div>
<?php endif; ?>
<?php
// Load KPI data for dashboard calculations
if (!defined('IGNY8_INCLUDE_CONFIG')) {
define('IGNY8_INCLUDE_CONFIG', true);
}
$kpi_config = $GLOBALS['igny8_kpi_config'] ?? []; // Loaded globally in igny8.php
$kpi_data = igny8_get_kpi_data_safe('planner_home', $kpi_config['planner_home'] ?? []);
// Calculate dashboard metrics
$total_keywords = $kpi_data['total_keywords'] ?? 0;
$mapped_keywords = $kpi_data['mapped_keywords'] ?? 0;
$total_clusters = $kpi_data['total_clusters'] ?? 0;
$clusters_with_ideas = $kpi_data['clusters_with_ideas'] ?? 0;
$total_ideas = $kpi_data['total_ideas'] ?? 0;
$queued_ideas = $kpi_data['queued_ideas'] ?? 0;
// Calculate percentages for progress bars
$keyword_mapping_pct = $total_keywords > 0 ? round(($mapped_keywords / $total_keywords) * 100) : 0;
$clusters_ideas_pct = $total_clusters > 0 ? round(($clusters_with_ideas / $total_clusters) * 100) : 0;
$ideas_queued_pct = $total_ideas > 0 ? round(($queued_ideas / $total_ideas) * 100) : 0;
// Use fixed colors matching top metric cards
$keyword_color = 'blue'; // Keywords Ready card is blue
$cluster_color = 'green'; // Clusters Built card is green
$idea_color = 'amber'; // Ideas Generated card is amber
?>
<!-- Planning Status Cards -->
<div class="igny8-dashboard-section">
<div class="igny8-grid-3 igny8-status-cards">
<!-- Keywords Ready Card -->
<div class="igny8-card igny8-status-card igny8-clickable-card igny8-status-blue" onclick="window.location.href='<?php echo admin_url('admin.php?page=igny8-planner&sm=keywords'); ?>'">
<div class="igny8-card-body">
<div class="igny8-status-metric">
<span class="igny8-status-count"><?php echo $total_keywords; ?></span>
<span class="igny8-status-label">Keywords Ready</span>
<span class="igny8-status-desc">Research, analyze, and manage keywords strategy</span>
</div>
<div class="igny8-status-icon">
<span class="dashicons dashicons-search igny8-dashboard-icon-sm"></span>
</div>
</div>
</div>
<!-- Clusters Built Card -->
<div class="igny8-card igny8-status-card igny8-clickable-card igny8-status-green" onclick="window.location.href='<?php echo admin_url('admin.php?page=igny8-planner&sm=clusters'); ?>'">
<div class="igny8-card-body">
<div class="igny8-status-metric">
<span class="igny8-status-count"><?php echo $total_clusters; ?></span>
<span class="igny8-status-label">Clusters Built</span>
<span class="igny8-status-desc">Organize keywords into strategic topical clusters</span>
</div>
<div class="igny8-status-icon">
<span class="dashicons dashicons-groups igny8-dashboard-icon-sm"></span>
</div>
</div>
</div>
<!-- Ideas Generated Card -->
<div class="igny8-card igny8-status-card igny8-clickable-card igny8-status-amber" onclick="window.location.href='<?php echo admin_url('admin.php?page=igny8-planner&sm=ideas'); ?>'">
<div class="igny8-card-body">
<div class="igny8-status-metric">
<span class="igny8-status-count"><?php echo $total_ideas; ?></span>
<span class="igny8-status-label">Ideas Generated</span>
<span class="igny8-status-desc">Generate creative content ideas based on semantic strategy</span>
</div>
<div class="igny8-status-icon">
<span class="dashicons dashicons-lightbulb igny8-dashboard-icon-sm"></span>
</div>
</div>
</div>
</div>
</div>
<!-- Planner Workflow Steps -->
<div class="igny8-dashboard-section">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Planner Workflow Steps</h3>
<p class="igny8-card-subtitle">Track your planning progress</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-update igny8-dashboard-icon-lg igny8-dashboard-icon-purple"></span>
</div>
</div>
</div>
<div class="igny8-grid igny8-grid-4">
<?php
// Step-by-Step UX Guide
$step_data = igny8_get_planner_step_data();
?>
<div class="igny8-card igny8-step-card <?php echo $step_data['keywords']['status']; ?>">
<div class="igny8-card-body">
<div class="igny8-step-header">
<div class="igny8-step-number">1</div>
<div class="igny8-step-title">Add Keywords</div>
</div>
<div class="igny8-step-status">
<span class="igny8-step-status-icon"><?php echo $step_data['keywords']['status'] === 'completed' ? '✅' : '⏳'; ?></span>
<span class="igny8-step-status-text"><?php echo $step_data['keywords']['status'] === 'completed' ? 'Completed' : 'Pending'; ?></span>
</div>
<div class="igny8-step-data">
<?php if ($step_data['keywords']['count'] > 0): ?>
<?php echo $step_data['keywords']['count']; ?> keywords added
<?php else: ?>
No keywords yet
<?php endif; ?>
</div>
<?php if ($step_data['keywords']['status'] === 'pending'): ?>
<div class="igny8-step-action">
<a href="?page=igny8-planner&sm=keywords" class="igny8-btn igny8-btn-primary igny8-btn-sm">Start Now</a>
</div>
<?php endif; ?>
</div>
</div>
<div class="igny8-card igny8-step-card <?php echo $step_data['sector']['status']; ?>">
<div class="igny8-card-body">
<div class="igny8-step-header">
<div class="igny8-step-number">2</div>
<div class="igny8-step-title">Select Sector</div>
</div>
<div class="igny8-step-status">
<span class="igny8-step-status-icon"><?php echo $step_data['sector']['status'] === 'completed' ? '✅' : '⚠'; ?></span>
<span class="igny8-step-status-text"><?php echo $step_data['sector']['status'] === 'completed' ? 'Selected' : 'Required'; ?></span>
</div>
<div class="igny8-step-data">
<?php if ($step_data['sector']['selected']): ?>
Sector configured
<?php else: ?>
Required for AI workflows
<?php endif; ?>
</div>
<?php if ($step_data['sector']['status'] === 'current'): ?>
<div class="igny8-step-action">
<a href="?page=igny8-planner" class="igny8-btn igny8-btn-primary igny8-btn-sm">Configure</a>
</div>
<?php endif; ?>
</div>
</div>
<div class="igny8-card igny8-step-card <?php echo $step_data['clusters']['status']; ?>">
<div class="igny8-card-body">
<div class="igny8-step-header">
<div class="igny8-step-number">3</div>
<div class="igny8-step-title">Auto Cluster</div>
</div>
<div class="igny8-step-status">
<span class="igny8-step-status-icon"><?php echo $step_data['clusters']['status'] === 'completed' ? '✅' : ($step_data['clusters']['status'] === 'current' ? '⏳' : '⏳'); ?></span>
<span class="igny8-step-status-text"><?php echo $step_data['clusters']['status'] === 'completed' ? 'Completed' : ($step_data['clusters']['status'] === 'current' ? 'Ready' : 'Pending'); ?></span>
</div>
<div class="igny8-step-data">
<?php if ($step_data['clusters']['unmapped_keywords'] > 0): ?>
<?php echo $step_data['clusters']['unmapped_keywords']; ?> unmapped keywords
<?php elseif ($step_data['clusters']['count'] > 0): ?>
<?php echo $step_data['clusters']['count']; ?> clusters created
<?php else: ?>
No clusters yet
<?php endif; ?>
</div>
<?php if ($step_data['clusters']['status'] === 'current'): ?>
<div class="igny8-step-action">
<a href="?page=igny8-planner&sm=clusters" class="igny8-btn igny8-btn-primary igny8-btn-sm">Start Now</a>
</div>
<?php endif; ?>
</div>
</div>
<div class="igny8-card igny8-step-card <?php echo $step_data['ideas']['status']; ?>">
<div class="igny8-card-body">
<div class="igny8-step-header">
<div class="igny8-step-number">4</div>
<div class="igny8-step-title">Generate Ideas</div>
</div>
<div class="igny8-step-status">
<span class="igny8-step-status-icon"><?php echo $step_data['ideas']['status'] === 'completed' ? '✅' : '⏳'; ?></span>
<span class="igny8-step-status-text"><?php echo $step_data['ideas']['status'] === 'completed' ? 'Completed' : 'Pending'; ?></span>
</div>
<div class="igny8-step-data">
<?php if ($step_data['ideas']['count'] > 0): ?>
<?php echo $step_data['ideas']['count']; ?> ideas generated
<?php else: ?>
No ideas yet
<?php endif; ?>
</div>
<?php if ($step_data['ideas']['status'] === 'pending' && $step_data['clusters']['count'] > 0): ?>
<div class="igny8-step-action">
<a href="?page=igny8-planner&sm=ideas" class="igny8-btn igny8-btn-primary igny8-btn-sm">Start Now</a>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- Planner Settings & AI Integration -->
<div class="igny8-dashboard-section">
<div class="igny8-flex-row">
<!-- AI Integration Settings Card -->
<div class="igny8-card">
<div class="igny8-flex-row">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Workflow Status & AI Settings</h3>
<p class="igny8-card-subtitle igny8-centered">Workflow Status & AI Settings</p>
</div>
</div>
<form id="igny8-ai-integration-form">
<div class="igny8-form-group">
<div class="igny8-mode-toggle-label">
<span class="igny8-mode-label">Manual</span>
<label class="igny8-toggle">
<input type="checkbox" name="igny8_planner_mode" value="ai" <?php checked(igny8_get_ai_setting('planner_mode', 'manual'), 'ai'); ?>>
<span class="igny8-toggle-slider"></span>
</label>
<span class="igny8-mode-label">AI Mode</span>
</div>
</div>
<div class="igny8-form-actions">
<button type="submit" class="igny8-btn igny8-btn-success">Save AI Integration Settings</button>
</div>
</form>
</div>
</div>
<!-- Sector Selection Settings Card -->
<div class="igny8-card">
<div class="igny8-flex-row">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Workflow Status & AI Settings</h3>
<p class="igny8-card-subtitle igny8-centered">Workflow Status & AI Settings</p>
</div>
</div>
<div id="igny8-sector-selection-container">
<!-- Initial state: Parent sector selection -->
<div id="igny8-parent-selection" class="igny8-selection-step">
<label for="igny8-parent-sector">Select Parent Sector:</label>
<select id="igny8-parent-sector" class="igny8-form-control">
<option value="">Choose a parent sector...</option>
</select>
<button id="igny8-lock-parent" class="igny8-btn igny8-btn-primary" style="display: none;">Lock Selection</button>
</div>
<!-- Child sectors selection -->
<div id="igny8-child-selection" class="igny8-selection-step" style="display: none;">
<h4>Select Child Sectors:</h4>
<div id="igny8-child-checkboxes" class="igny8-checkbox-group">
<!-- Child checkboxes will be populated here -->
</div>
<button id="igny8-save-selection" class="igny8-btn igny8-btn-success">Save Selection</button>
</div>
<!-- Final selection display -->
<div id="igny8-final-selection" class="igny8-selection-step" style="display: none;">
<div id="igny8-selected-sectors-display">
<!-- Selected sectors will be displayed here -->
</div>
<button id="igny8-edit-selection" class="igny8-btn igny8-btn-outline">Remove or Edit My Selection</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="igny8-grid-3">
<!-- Progress / Readiness Summary -->
<div class="igny8-dashboard-section">
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Progress & Readiness Summary</h3>
<p class="igny8-card-subtitle">Planning workflow progress tracking</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-chart-area igny8-dashboard-icon-lg igny8-dashboard-icon-blue"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<!-- Keyword Mapping Progress -->
<div class="igny8-progress-item">
<div class="igny8-progress-header">
<span class="igny8-progress-label">Keyword Mapping</span>
<span class="igny8-progress-percent"><?php echo $keyword_mapping_pct; ?>%</span>
</div>
<div class="igny8-progress-bar">
<div class="igny8-progress-fill igny8-progress-<?php echo $keyword_color; ?>" style="width: <?php echo $keyword_mapping_pct; ?>%"></div>
</div>
<div class="igny8-progress-details"><?php echo $mapped_keywords; ?> of <?php echo $total_keywords; ?> keywords mapped</div>
</div>
<!-- Clusters With Ideas Progress -->
<div class="igny8-progress-item">
<div class="igny8-progress-header">
<span class="igny8-progress-label">Clusters With Ideas</span>
<span class="igny8-progress-percent"><?php echo $clusters_ideas_pct; ?>%</span>
</div>
<div class="igny8-progress-bar">
<div class="igny8-progress-fill igny8-progress-<?php echo $cluster_color; ?>" style="width: <?php echo $clusters_ideas_pct; ?>%"></div>
</div>
<div class="igny8-progress-details"><?php echo $clusters_with_ideas; ?> of <?php echo $total_clusters; ?> clusters have ideas</div>
</div>
<!-- Ideas Queued to Writer Progress -->
<div class="igny8-progress-item">
<div class="igny8-progress-header">
<span class="igny8-progress-label">Ideas Queued to Writer</span>
<span class="igny8-progress-percent"><?php echo $ideas_queued_pct; ?>%</span>
</div>
<div class="igny8-progress-bar">
<div class="igny8-progress-fill igny8-progress-<?php echo $idea_color; ?>" style="width: <?php echo $ideas_queued_pct; ?>%"></div>
</div>
<div class="igny8-progress-details"><?php echo $queued_ideas; ?> of <?php echo $total_ideas; ?> ideas queued</div>
</div>
</div>
</div>
</div>
<!-- Top 5 Clusters by Volume -->
<div class="igny8-dashboard-section">
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Top 5 Clusters by Volume</h3>
<p class="igny8-card-subtitle">Highest volume keyword clusters</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-chart-bar igny8-dashboard-icon-lg igny8-dashboard-icon-green"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<?php
// Get top 5 clusters by volume
global $wpdb;
$top_clusters = $wpdb->get_results("
SELECT
c.cluster_name,
c.total_volume,
c.keyword_count
FROM {$wpdb->prefix}igny8_clusters c
WHERE c.status = 'active' AND c.total_volume > 0
ORDER BY c.total_volume DESC
LIMIT 5
");
if ($top_clusters):
$max_volume = $top_clusters[0]->total_volume; // Highest volume for percentage calculation
$color_classes = ['igny8-progress-blue', 'igny8-progress-green', 'igny8-progress-amber', 'igny8-progress-purple', 'igny8-progress-text-dim'];
?>
<div class="igny8-analytics-list">
<?php foreach ($top_clusters as $index => $cluster):
$percentage = $max_volume > 0 ? round(($cluster->total_volume / $max_volume) * 100) : 0;
$color_class = $color_classes[$index % 5];
?>
<div class="igny8-analytics-item">
<div class="igny8-item-info">
<div class="igny8-item-label"><?php echo esc_html($cluster->cluster_name); ?></div>
<div class="igny8-item-value"><?php echo number_format($cluster->total_volume); ?></div>
</div>
<div class="igny8-item-progress">
<div class="igny8-progress-track">
<div class="igny8-progress-fill <?php echo $color_class; ?>" style="width: <?php echo $percentage; ?>%"></div>
</div>
<span class="igny8-progress-percent"><?php echo $percentage; ?>%</span>
</div>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="igny8-empty-analytics">
<span class="dashicons dashicons-info igny8-dashboard-icon-dim"></span>
<p>No clusters found yet</p>
<a href="<?php echo admin_url('admin.php?page=igny8-planner&sm=clusters'); ?>" class="igny8-btn igny8-btn-outline igny8-btn-sm">View Clusters</a>
</div>
<?php endif; ?>
</div>
</div>
</div>
<!-- Ideas by Status -->
<div class="igny8-dashboard-section">
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Ideas by Status</h3>
<p class="igny8-card-subtitle">Content ideas workflow status</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-lightbulb igny8-dashboard-icon-lg igny8-dashboard-icon-amber"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<?php
// Get ideas by status
$ideas_by_status = $wpdb->get_results("
SELECT
status,
COUNT(*) as count
FROM {$wpdb->prefix}igny8_content_ideas
GROUP BY status
ORDER BY count DESC
");
if ($ideas_by_status):
$total_ideas_status = array_sum(array_column($ideas_by_status, 'count'));
$status_colors = [
'new' => 'igny8-progress-blue',
'scheduled' => 'igny8-progress-amber',
'published' => 'igny8-progress-green',
'draft' => 'igny8-progress-purple',
'completed' => 'igny8-progress-green'
];
?>
<div class="igny8-analytics-list">
<?php foreach ($ideas_by_status as $status):
$percentage = $total_ideas_status > 0 ? round(($status->count / $total_ideas_status) * 100) : 0;
$color_class = $status_colors[$status->status] ?? 'igny8-progress-text-dim';
$status_display = ucfirst(str_replace('_', ' ', $status->status));
?>
<div class="igny8-analytics-item">
<div class="igny8-item-info">
<div class="igny8-item-label"><?php echo esc_html($status_display); ?></div>
<div class="igny8-item-value"><?php echo number_format($status->count); ?></div>
</div>
<div class="igny8-item-progress">
<div class="igny8-progress-track">
<div class="igny8-progress-fill <?php echo $color_class; ?>" style="width: <?php echo $percentage; ?>%"></div>
</div>
<span class="igny8-progress-percent"><?php echo $percentage; ?>%</span>
</div>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="igny8-empty-analytics">
<span class="dashicons dashicons-info igny8-dashboard-icon-dim"></span>
<p>No ideas found yet</p>
<a href="<?php echo admin_url('admin.php?page=igny8-planner&sm=ideas'); ?>" class="igny8-btn igny8-btn-outline igny8-btn-sm">View Ideas</a>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- Next Actions Panel -->
<div class="igny8-dashboard-section">
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Next Actions</h3>
<p class="igny8-card-subtitle">Actionable items requiring attention</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-arrow-right-alt igny8-dashboard-icon-lg igny8-dashboard-icon-purple"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<div class="igny8-next-actions">
<?php
$unmapped_keywords = $kpi_data['unmapped_keywords'] ?? 0;
$clusters_without_ideas = $total_clusters - $clusters_with_ideas;
$ideas_not_queued = $total_ideas - $queued_ideas;
?>
<?php if ($unmapped_keywords > 0): ?>
<div class="igny8-action-item">
<span class="igny8-action-text"><?php echo $unmapped_keywords; ?> keywords unmapped</span>
<a href="<?php echo admin_url('admin.php?page=igny8-planner&sm=keywords&filter_status=unmapped'); ?>" class="igny8-btn igny8-btn-text">Map Keywords</a>
</div>
<?php endif; ?>
<?php if ($clusters_without_ideas > 0): ?>
<div class="igny8-action-item">
<span class="igny8-action-text"><?php echo $clusters_without_ideas; ?> clusters without ideas</span>
<a href="<?php echo admin_url('admin.php?page=igny8-planner&sm=ideas'); ?>" class="igny8-btn igny8-btn-text">Generate Ideas</a>
</div>
<?php endif; ?>
<?php if ($ideas_not_queued > 0): ?>
<div class="igny8-action-item">
<span class="igny8-action-text"><?php echo $ideas_not_queued; ?> ideas not queued to writer</span>
<a href="<?php echo admin_url('admin.php?page=igny8-planner&sm=ideas&filter_status=new'); ?>" class="igny8-btn igny8-btn-text">Queue to Writer</a>
</div>
<?php endif; ?>
<div class="igny8-action-item">
<span class="igny8-action-text">Import new keywords to expand your strategy</span>
<a href="<?php echo admin_url('admin.php?page=igny8-import-export'); ?>" class="igny8-btn igny8-btn-text">Import Keywords</a>
</div>
<?php if ($unmapped_keywords == 0 && $clusters_without_ideas == 0 && $ideas_not_queued == 0): ?>
<div class="igny8-action-item igny8-action-complete">
<span class="igny8-action-text">All planning tasks completed!</span>
<span class="igny8-action-status">✓ Ready for content creation</span>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php
// Set submodule for subheader display - show keywords charts on home page
$current_submodule = 'keywords';
// Home page - add sector selection functionality
// Only load CRON key if this page needs CRON functionality
$cron_key = igny8_needs_cron_functionality() ? igny8_get_or_generate_cron_key() : null;
// Debug logging (remove in production)
// if (defined('WP_DEBUG') && WP_DEBUG) {
// error_log('Igny8 Planner: cronKey being passed to JS: ' . $cron_key);
// }
wp_localize_script('igny8-admin-js', 'IGNY8_PAGE', [
'module' => 'planner',
'submodule' => 'home',
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('igny8_planner_settings'),
'cronKey' => $cron_key
]);
break;
}
// Capture page content
$igny8_page_content = ob_get_clean();
// Include global layout
include_once plugin_dir_path(__FILE__) . '../../core/global-layout.php';

View File

@@ -0,0 +1,676 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : general-settings.php
* @location : /modules/settings/general-settings.php
* @type : Admin Page
* @scope : Module Only
* @allowed : Settings configuration, subpage routing, plugin preferences
* @reusability : Single Use
* @notes : Main settings page with subpage routing for settings module
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Initialize module manager and render the complete page
$module_manager = igny8_module_manager();
// Handle URL parameters for subpages
$subpage = $_GET['sp'] ?? 'general';
// Start output buffering
ob_start();
switch ($subpage) {
case 'status':
include plugin_dir_path(__FILE__) . 'status.php';
break;
case 'integration':
include plugin_dir_path(__FILE__) . 'integration.php';
break;
case 'schedules':
include plugin_dir_path(__FILE__) . 'schedules.php';
break;
case 'import-export':
include plugin_dir_path(__FILE__) . 'import-export.php';
break;
case 'general':
default:
// General settings content
// Handle image generation settings form submission
if (isset($_POST['igny8_image_settings_nonce']) && wp_verify_nonce($_POST['igny8_image_settings_nonce'], 'igny8_image_settings')) {
$image_type = sanitize_text_field($_POST['igny8_image_type'] ?? 'realistic');
$desktop_enabled = isset($_POST['igny8_desktop_enabled']) ? '1' : '0';
$mobile_enabled = isset($_POST['igny8_mobile_enabled']) ? '1' : '0';
$max_in_article_images = intval($_POST['igny8_max_in_article_images'] ?? 1);
$image_format = sanitize_text_field($_POST['igny8_image_format'] ?? 'jpg');
update_option('igny8_image_type', $image_type);
update_option('igny8_desktop_enabled', $desktop_enabled);
update_option('igny8_mobile_enabled', $mobile_enabled);
update_option('igny8_max_in_article_images', $max_in_article_images);
update_option('igny8_image_format', $image_format);
echo '<div class="notice notice-success"><p>Image generation settings saved successfully!</p></div>';
} elseif (isset($_POST['igny8_image_settings_nonce'])) {
echo '<div class="notice notice-error"><p>Security check failed. Please try again.</p></div>';
}
// Handle editor type settings form submission
if (isset($_POST['igny8_editor_type_nonce']) && wp_verify_nonce($_POST['igny8_editor_type_nonce'], 'igny8_editor_type_settings')) {
$editor_type = isset($_POST['igny8_editor_type']) ? sanitize_text_field($_POST['igny8_editor_type']) : 'block';
update_option('igny8_editor_type', $editor_type);
echo '<div class="notice notice-success"><p>Editor type settings saved successfully! Selected: ' . esc_html($editor_type) . '</p></div>';
} elseif (isset($_POST['igny8_editor_type_nonce'])) {
echo '<div class="notice notice-error"><p>Security check failed. Please try again.</p></div>';
}
// Handle image metabox settings form submission
if (isset($_POST['igny8_image_metabox_nonce']) && wp_verify_nonce($_POST['igny8_image_metabox_nonce'], 'igny8_image_metabox_settings')) {
$enabled_types = isset($_POST['igny8_enable_image_metabox']) ? $_POST['igny8_enable_image_metabox'] : [];
$enabled_types = array_map('sanitize_text_field', $enabled_types);
update_option('igny8_enable_image_metabox', $enabled_types);
echo '<div class="notice notice-success"><p>Image metabox settings saved successfully!</p></div>';
}
// Handle module settings form submission (only if not editor type or image metabox form)
if (isset($_POST['submit']) && !isset($_POST['igny8_editor_type_nonce']) && !isset($_POST['igny8_image_metabox_nonce'])) {
$module_manager->save_module_settings();
}
// Debug: Log form submission data (remove in production)
if (isset($_POST['submit']) && current_user_can('manage_options')) {
error_log('Igny8 Settings Debug - POST data: ' . print_r($_POST, true));
}
$settings = get_option('igny8_module_settings', []);
?>
<div class="igny8-settings-section">
<!-- Module Manager Section -->
<div class="igny8-settings-section">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Module Manager</h3>
<p class="igny8-card-subtitle">Enable or disable plugin modules and features</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-tools igny8-dashboard-icon-lg igny8-dashboard-icon-blue"></span>
</div>
</div>
</div>
<!-- Module Settings -->
<div class="igny8-settings-section">
<div class="igny8-settings">
<form method="post" action="">
<?php wp_nonce_field('igny8_module_settings', 'igny8_module_nonce'); ?>
<!-- Main Modules Section -->
<div class="igny8-settings-section">
<div class="igny8-module-cards-grid">
<?php foreach ($module_manager->get_modules() as $module_key => $module): ?>
<?php if ($module['category'] === 'main'): ?>
<div class="igny8-card igny8-module-card">
<div class="igny8-module-header">
<div class="igny8-card-title">
<span class="dashicons <?php echo esc_attr($module['icon']); ?>" style="color: var(--blue); font-size: 20px; margin-right: 10px;"></span>
<h6><?php echo esc_html($module['name']); ?></h6>
</div>
<div class="igny8-module-toggle">
<label class="igny8-toggle-switch">
<input type="checkbox"
id="module_<?php echo esc_attr($module_key); ?>"
name="igny8_module_settings[<?php echo esc_attr($module_key); ?>]"
value="1"
<?php checked($module_manager->is_module_enabled($module_key)); ?>>
<span class="igny8-toggle-slider"></span>
</label>
</div>
</div>
<div class="igny8-module-description">
<p><?php echo esc_html($module['description']); ?></p>
</div>
</div>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
<!-- Admin & Analytics Section -->
<div class="igny8-settings-section">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Admin & Analytics</h3>
<p class="igny8-card-subtitle">Administrative tools and analytics modules</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-chart-bar igny8-dashboard-icon-lg igny8-dashboard-icon-green"></span>
</div>
</div>
</div>
<div class="igny8-module-cards-grid">
<?php foreach ($module_manager->get_modules() as $module_key => $module): ?>
<?php if ($module['category'] === 'admin'): ?>
<div class="igny8-card igny8-module-card">
<div class="igny8-module-header">
<div class="igny8-card-title">
<span class="dashicons <?php echo esc_attr($module['icon']); ?>" style="color: var(--green); font-size: 20px; margin-right: 10px;"></span>
<h6><?php echo esc_html($module['name']); ?></h6>
</div>
<div class="igny8-module-toggle">
<label class="igny8-toggle-switch">
<input type="checkbox"
id="module_<?php echo esc_attr($module_key); ?>"
name="igny8_module_settings[<?php echo esc_attr($module_key); ?>]"
value="1"
<?php checked($module_manager->is_module_enabled($module_key)); ?>>
<span class="igny8-toggle-slider"></span>
</label>
</div>
</div>
<div class="igny8-module-description">
<p><?php echo esc_html($module['description']); ?></p>
</div>
</div>
<?php endif; ?>
<?php endforeach; ?>
</div>
</div>
<?php submit_button('Save Module Settings', 'primary', 'submit', true, ['style' => 'margin-top: 20px;']); ?>
</form>
</div>
</div>
</div>
<!-- Editor Type Selection Section -->
<div class="igny8-settings-section">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Content Editor Type</h3>
<p class="igny8-card-subtitle">Choose between Classic editor or Block (Gutenberg) editor for AI-generated content</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-edit-page igny8-dashboard-icon-lg igny8-dashboard-icon-green"></span>
</div>
</div>
</div>
<div class="igny8-settings">
<form method="post" action="">
<?php wp_nonce_field('igny8_editor_type_settings', 'igny8_editor_type_nonce'); ?>
<div class="igny8-form-group">
<label for="igny8_editor_type"><strong>Select Editor Type:</strong></label>
<div style="margin-top: 15px;">
<?php
$current_editor_type = get_option('igny8_editor_type', 'block');
?>
<!-- Current Setting Display -->
<div style="background: #e8f4fd; border: 1px solid #0073aa; padding: 10px; border-radius: 4px; margin-bottom: 15px;">
<strong>Current Setting:</strong>
<span style="color: #0073aa; font-weight: 600;">
<?php echo $current_editor_type === 'block' ? 'Block Editor (Gutenberg)' : 'Classic Editor'; ?>
</span>
</div>
<label class="igny8-editor-option <?php echo $current_editor_type === 'block' ? 'selected' : ''; ?>">
<input type="radio" name="igny8_editor_type" value="block" <?php checked($current_editor_type, 'block'); ?>>
<div class="igny8-editor-option-content">
<div class="igny8-editor-option-title">Block Editor (Gutenberg)</div>
<div class="igny8-editor-option-description">
Modern block-based editor with advanced formatting options. Recommended for better content structure and WordPress compatibility.
</div>
</div>
</label>
<label class="igny8-editor-option <?php echo $current_editor_type === 'classic' ? 'selected' : ''; ?>">
<input type="radio" name="igny8_editor_type" value="classic" <?php checked($current_editor_type, 'classic'); ?>>
<div class="igny8-editor-option-content">
<div class="igny8-editor-option-title">Classic Editor</div>
<div class="igny8-editor-option-description">
Traditional WYSIWYG editor. Choose this if you prefer the classic WordPress editing experience.
</div>
</div>
</label>
</div>
<div style="background: #f8f9fa; border: 1px solid #e9ecef; padding: 15px; border-radius: 6px; margin-top: 20px;">
<p style="margin: 0 0 10px 0; font-weight: bold; color: #495057;">📝 <strong>How this affects your content:</strong></p>
<ul style="margin: 0; padding-left: 20px; color: #6c757d; font-size: 14px;">
<li><strong>Block Editor:</strong> AI content will be converted to WordPress blocks for better formatting and structure</li>
<li><strong>Classic Editor:</strong> AI content will be saved as HTML and displayed in the classic editor</li>
<li>You can change this setting anytime and it will apply to all new AI-generated content</li>
</ul>
</div>
</div>
<?php submit_button('Save Editor Settings', 'primary', 'submit', true, ['style' => 'margin-top: 20px;']); ?>
</form>
</div>
</div>
<script>
// Enhanced editor selection interaction
document.addEventListener('DOMContentLoaded', function() {
const editorOptions = document.querySelectorAll('.igny8-editor-option');
editorOptions.forEach(option => {
const radio = option.querySelector('input[type="radio"]');
// Add click handler to the entire label
option.addEventListener('click', function(e) {
if (e.target !== radio) {
radio.checked = true;
updateSelection();
}
});
// Add change handler to radio buttons
radio.addEventListener('change', function() {
updateSelection();
});
});
function updateSelection() {
editorOptions.forEach(option => {
const radio = option.querySelector('input[type="radio"]');
if (radio.checked) {
option.classList.add('selected');
} else {
option.classList.remove('selected');
}
});
}
// Initialize selection on page load
updateSelection();
});
</script>
<!-- Image Generation Settings Section -->
<div class="igny8-settings-section">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Image Generation</h3>
<p class="igny8-card-subtitle">Configure AI image generation settings and preferences</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-format-image igny8-dashboard-icon-lg igny8-dashboard-icon-blue"></span>
</div>
</div>
</div>
<div class="igny8-settings">
<form method="post" action="" id="igny8-image-generation-form">
<?php wp_nonce_field('igny8_image_settings', 'igny8_image_settings_nonce'); ?>
<div class="igny8-form-group">
<label><strong>Image Settings</strong></label>
<div style="display: flex; gap: 20px; align-items: end; margin-top: 10px;">
<div style="flex: 1;">
<label for="igny8_image_type" style="display: block; margin-bottom: 5px; font-weight: 500;">Image Type</label>
<select id="igny8_image_type" name="igny8_image_type" class="igny8-form-control">
<option value="realistic" <?php selected(get_option('igny8_image_type', 'realistic'), 'realistic'); ?>>Realistic</option>
<option value="illustration" <?php selected(get_option('igny8_image_type', 'realistic'), 'illustration'); ?>>Illustration</option>
<option value="3D render" <?php selected(get_option('igny8_image_type', 'realistic'), '3D render'); ?>>3D Render</option>
<option value="minimalist" <?php selected(get_option('igny8_image_type', 'realistic'), 'minimalist'); ?>>Minimalist</option>
<option value="cartoon" <?php selected(get_option('igny8_image_type', 'realistic'), 'cartoon'); ?>>Cartoon</option>
</select>
</div>
<div style="flex: 1;">
<label for="igny8_max_in_article_images" style="display: block; margin-bottom: 5px; font-weight: 500;">Max In-Article Images</label>
<select id="igny8_max_in_article_images" name="igny8_max_in_article_images" class="igny8-form-control">
<option value="1" <?php selected(get_option('igny8_max_in_article_images', '1'), '1'); ?>>1 Image</option>
<option value="2" <?php selected(get_option('igny8_max_in_article_images', '1'), '2'); ?>>2 Images</option>
<option value="3" <?php selected(get_option('igny8_max_in_article_images', '1'), '3'); ?>>3 Images</option>
<option value="4" <?php selected(get_option('igny8_max_in_article_images', '1'), '4'); ?>>4 Images</option>
<option value="5" <?php selected(get_option('igny8_max_in_article_images', '1'), '5'); ?>>5 Images</option>
</select>
</div>
<div style="flex: 1;">
<label for="igny8_image_format" style="display: block; margin-bottom: 5px; font-weight: 500;">Image Format</label>
<select id="igny8_image_format" name="igny8_image_format" class="igny8-form-control">
<option value="jpg" <?php selected(get_option('igny8_image_format', 'jpg'), 'jpg'); ?>>JPG</option>
<option value="png" <?php selected(get_option('igny8_image_format', 'jpg'), 'png'); ?>>PNG</option>
<option value="webp" <?php selected(get_option('igny8_image_format', 'jpg'), 'webp'); ?>>WEBP</option>
</select>
<div id="igny8-selected-format" style="margin-top: 5px; font-weight: bold; color: #0073aa;"></div>
</div>
</div>
</div>
<div class="igny8-form-group">
<label><strong>Current Image Provider & Model</strong></label>
<div style="background: #f8f9fa; border: 1px solid #e9ecef; padding: 15px; border-radius: 6px; margin-top: 10px;">
<?php
$current_service = get_option('igny8_image_service', 'openai');
$current_model = get_option('igny8_image_model', 'dall-e-3');
$current_runware_model = get_option('igny8_runware_model', 'runware:97@1');
if ($current_service === 'openai') {
$model_names = [
'dall-e-3' => 'DALL·E 3',
'dall-e-2' => 'DALL·E 2',
'gpt-image-1' => 'GPT Image 1 (Full)',
'gpt-image-1-mini' => 'GPT Image 1 Mini'
];
$model_name = $model_names[$current_model] ?? $current_model;
echo '<div style="display: flex; align-items: center; margin-bottom: 8px;">';
echo '<span style="font-weight: bold; color: #0073aa; margin-right: 10px;">Provider:</span>';
echo '<span style="color: #495057;">OpenAI</span>';
echo '</div>';
echo '<div style="display: flex; align-items: center;">';
echo '<span style="font-weight: bold; color: #0073aa; margin-right: 10px;">Model:</span>';
echo '<span style="color: #495057;">' . esc_html($model_name) . '</span>';
echo '</div>';
} else {
echo '<div style="display: flex; align-items: center; margin-bottom: 8px;">';
echo '<span style="font-weight: bold; color: #0073aa; margin-right: 10px;">Provider:</span>';
echo '<span style="color: #495057;">Runware</span>';
echo '</div>';
echo '<div style="display: flex; align-items: center;">';
echo '<span style="font-weight: bold; color: #0073aa; margin-right: 10px;">Model:</span>';
echo '<span style="color: #495057;">HiDream-I1 Full</span>';
echo '</div>';
}
?>
<div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #dee2e6;">
<small style="color: #6c757d;">
<strong>Note:</strong> To change the image provider or model, go to
<a href="<?php echo admin_url('admin.php?page=igny8-settings&sp=integration'); ?>" style="color: #0073aa;">Integration Settings</a>
</small>
</div>
</div>
</div>
<div class="igny8-form-group">
<label><strong>Image Size Options</strong></label>
<div class="igny8-size-checkbox-container">
<!-- Featured Image - Simple row without checkbox/count -->
<div class="igny8-size-option igny8-featured-image-row">
<div class="igny8-size-info igny8-size-featured">Featured Image</div>
<div class="igny8-size-info" id="featured-size-info">1280×832 pixels</div>
</div>
<div class="igny8-size-option">
<label class="igny8-checkbox-label">
<input type="checkbox" id="igny8_desktop_enabled" name="igny8_desktop_enabled" value="1" <?php checked(get_option('igny8_desktop_enabled', '1'), '1'); ?>>
<span class="igny8-checkbox-text">Desktop Images</span>
</label>
<div class="igny8-size-info" id="desktop-size-info">1024×1024 pixels</div>
</div>
<div class="igny8-size-option">
<label class="igny8-checkbox-label">
<input type="checkbox" id="igny8_mobile_enabled" name="igny8_mobile_enabled" value="1" <?php checked(get_option('igny8_mobile_enabled', '0'), '1'); ?>>
<span class="igny8-checkbox-text">Mobile Images</span>
</label>
<div class="igny8-size-info" id="mobile-size-info">960×1280 pixels</div>
</div>
</div>
<small class="form-help">Choose which image sizes to generate and how many of each type.</small>
</div>
<?php submit_button('Save Image Settings', 'primary', 'submit', true, ['style' => 'margin-top: 20px;']); ?>
</form>
</div>
</div>
<script>
// Image generation settings JavaScript
document.addEventListener('DOMContentLoaded', function() {
// Get current provider from integration settings
const currentProvider = '<?php echo esc_js(get_option('igny8_image_service', 'openai')); ?>';
function updateSizeInfo(provider) {
const sizeInfo = {
'runware': {
featured: '1280×832 pixels',
desktop: '1024×1024 pixels',
mobile: '960×1280 pixels'
},
'openai': {
featured: '1024×1024 pixels',
desktop: '1024×1024 pixels',
mobile: '1024×1024 pixels'
},
'dalle': {
featured: '1024×1024 pixels',
desktop: '1024×1024 pixels',
mobile: '1024×1024 pixels'
}
};
if (sizeInfo[provider]) {
$('#featured-size-info').text(sizeInfo[provider].featured);
$('#desktop-size-info').text(sizeInfo[provider].desktop);
$('#mobile-size-info').text(sizeInfo[provider].mobile);
}
}
// Initialize with current provider
updateSizeInfo(currentProvider);
// Image format selector functionality
const $formatSelector = $('#igny8_image_format');
const $formatDisplay = $('#igny8-selected-format');
$formatSelector.on('change', function() {
const selectedFormat = $(this).val().toUpperCase();
$formatDisplay.text(selectedFormat + ' format');
});
// Initialize format display with saved value
const savedFormat = '<?php echo esc_js(get_option('igny8_image_format', 'jpg')); ?>';
$formatSelector.val(savedFormat);
$formatSelector.trigger('change');
});
</script>
<style>
/* Image Generation Settings Styles */
.igny8-size-checkbox-container {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 20px;
margin-top: 10px;
}
.igny8-size-option {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #e9ecef;
}
.igny8-size-option:last-child {
border-bottom: none;
}
.igny8-featured-image-row {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 6px;
padding: 15px 20px;
margin-bottom: 15px;
border-bottom: none;
}
.igny8-size-featured {
font-weight: bold;
font-size: 16px;
}
.igny8-checkbox-label {
display: flex;
align-items: center;
cursor: pointer;
font-weight: 500;
}
.igny8-checkbox-label input[type="checkbox"] {
margin-right: 10px;
transform: scale(1.2);
}
.igny8-size-info {
font-size: 14px;
color: #6c757d;
font-weight: 500;
}
.igny8-featured-image-row .igny8-size-info {
color: rgba(255, 255, 255, 0.9);
}
.form-help {
display: block;
margin-top: 5px;
font-size: 13px;
color: #6c757d;
font-style: italic;
}
</style>
<!-- Image Metabox Settings Section -->
<div class="igny8-settings-section">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>In-Article Image Meta Box</h3>
<p class="igny8-card-subtitle">Enable image metabox for specific post types</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-format-image igny8-dashboard-icon-lg igny8-dashboard-icon-blue"></span>
</div>
</div>
</div>
<div class="igny8-settings">
<form method="post" action="">
<?php wp_nonce_field('igny8_image_metabox_settings', 'igny8_image_metabox_nonce'); ?>
<div class="igny8-form-group">
<label for="igny8_enable_image_metabox"><strong>Enable In-Article Image Meta Box For:</strong></label>
<div style="max-height: 200px; overflow-y: auto; border: 1px solid #ddd; padding: 15px; background: #f9f9f9; border-radius: 4px; margin-top: 10px;">
<?php
$all_post_types = get_post_types(['public' => true], 'objects');
$enabled_types = (array) get_option('igny8_enable_image_metabox', []);
foreach ($all_post_types as $pt => $obj) {
$checked = in_array($pt, $enabled_types) ? 'checked' : '';
echo '<label style="display: block; margin-bottom: 8px; padding: 5px;">';
echo '<input type="checkbox" name="igny8_enable_image_metabox[]" value="' . esc_attr($pt) . '" ' . $checked . ' style="margin-right: 8px;"> ';
echo '<strong>' . esc_html($obj->label) . '</strong> <em>(' . esc_html($pt) . ')</em>';
echo '</label>';
}
?>
</div>
<p class="description" style="margin-top: 10px;">Select which post types should display the In-Article Image metabox in the WordPress editor.</p>
</div>
<!-- Shortcode Usage Examples -->
<div class="igny8-form-group" style="margin-top: 25px;">
<label><strong>Shortcode Usage Examples:</strong></label>
<div style="background: #f8f9fa; border: 1px solid #e9ecef; padding: 15px; border-radius: 4px; margin-top: 10px;">
<p style="margin: 0 0 10px 0; font-weight: bold; color: #495057;">Use these shortcodes in your posts/pages to display the selected images:</p>
<div style="margin-bottom: 12px;">
<code style="background: #fff; padding: 4px 8px; border: 1px solid #ddd; border-radius: 3px; font-size: 13px;">[igny8-images]</code>
<span style="margin-left: 8px; color: #6c757d; font-size: 13px;">Display all images</span>
</div>
<div style="margin-bottom: 12px;">
<code style="background: #fff; padding: 4px 8px; border: 1px solid #ddd; border-radius: 3px; font-size: 13px;">[igny8-image id="desktop-1"]</code>
<span style="margin-left: 8px; color: #6c757d; font-size: 13px;">Display specific image by ID</span>
</div>
<div style="margin-bottom: 12px;">
<code style="background: #fff; padding: 4px 8px; border: 1px solid #ddd; border-radius: 3px; font-size: 13px;">[igny8-desktop-images]</code>
<span style="margin-left: 8px; color: #6c757d; font-size: 13px;">Display only desktop images</span>
</div>
<div style="margin-bottom: 12px;">
<code style="background: #fff; padding: 4px 8px; border: 1px solid #ddd; border-radius: 3px; font-size: 13px;">[igny8-mobile-images]</code>
<span style="margin-left: 8px; color: #6c757d; font-size: 13px;">Display only mobile images</span>
</div>
<div style="margin-bottom: 12px;">
<code style="background: #fff; padding: 4px 8px; border: 1px solid #ddd; border-radius: 3px; font-size: 13px;">[igny8-responsive-gallery]</code>
<span style="margin-left: 8px; color: #6c757d; font-size: 13px;">Responsive gallery (desktop on large screens, mobile on small screens)</span>
</div>
<div style="margin-bottom: 0;">
<code style="background: #fff; padding: 4px 8px; border: 1px solid #ddd; border-radius: 3px; font-size: 13px;">[igny8-image-count]</code>
<span style="margin-left: 8px; color: #6c757d; font-size: 13px;">Display count of images</span>
</div>
<p style="margin: 15px 0 0 0; font-size: 12px; color: #6c757d; font-style: italic;">
💡 <strong>Tip:</strong> After selecting images in the metabox, use these shortcodes in your post content to display them on the frontend.
</p>
</div>
</div>
<?php submit_button('Save Image Metabox Settings', 'primary', 'submit', true, ['style' => 'margin-top: 20px;']); ?>
</form>
</div>
</div>
<!-- Table Settings Section -->
<div class="igny8-settings-section">
<form method="post" action="options.php">
<?php settings_fields('igny8_table_settings'); ?>
<table class="form-table">
<tr>
<th scope="row">
<label for="igny8_records_per_page">Records Per Page</label>
</th>
<td>
<select name="igny8_records_per_page" id="igny8_records_per_page">
<option value="10" <?php selected(get_option('igny8_records_per_page', 20), 10); ?>>10</option>
<option value="20" <?php selected(get_option('igny8_records_per_page', 20), 20); ?>>20</option>
<option value="50" <?php selected(get_option('igny8_records_per_page', 20), 50); ?>>50</option>
<option value="100" <?php selected(get_option('igny8_records_per_page', 20), 100); ?>>100</option>
</select>
<p class="description">Default number of records to display per page across all tables in the plugin.</p>
</td>
</tr>
</table>
<?php submit_button('Save Settings'); ?>
</form>
</div>
</div>
<?php
break;
}
// Capture page content
$igny8_page_content = ob_get_clean();
// Include global layout
include plugin_dir_path(__FILE__) . '../../core/global-layout.php';
?>

View File

@@ -0,0 +1,267 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : import-export.php
* @location : /modules/settings/import-export.php
* @type : Admin Page
* @scope : Module Only
* @allowed : Data import/export, backup operations, data transfer
* @reusability : Single Use
* @notes : Import/export settings page for settings module
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Start output buffering
ob_start();
// Get current import/export settings
$import_export_settings = get_option('igny8_import_export_settings', [
'default_format' => 'csv',
'overwrite_existing' => false,
'include_metrics' => true,
'file_naming_pattern' => 'igny8_export_[date].csv'
]);
// Get recent import/export logs
$recent_logs = get_option('igny8_import_export_logs', []);
$recent_logs = array_slice($recent_logs, 0, 10); // Last 10 entries
// Script localization will be done in the JavaScript section below
?>
<div class="igny8-import-export-page">
<div class="igny8-container">
<!-- Import Data Section -->
<div class="igny8-card igny8-mb-20">
<div class="igny8-card-header igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Import Data</h3>
<div class="igny8-card-subtitle">Download CSV templates and import your data into the Planner module</div>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-upload igny8-dashboard-icon-sm"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<!-- Template Downloads -->
<div class="igny8-mb-20">
<h4>Download Templates</h4>
<div class="igny8-grid-2 igny8-gap-10">
<a href="<?php echo admin_url('admin-ajax.php?action=igny8_download_template&template_type=keywords&nonce=' . wp_create_nonce('igny8_import_export_nonce')); ?>" class="igny8-btn igny8-btn-outline">
<span class="dashicons dashicons-download"></span> Keywords Template
</a>
<a href="<?php echo admin_url('admin-ajax.php?action=igny8_download_template&template_type=clusters&nonce=' . wp_create_nonce('igny8_import_export_nonce')); ?>" class="igny8-btn igny8-btn-outline">
<span class="dashicons dashicons-download"></span> Clusters Template
</a>
<a href="<?php echo admin_url('admin-ajax.php?action=igny8_download_template&template_type=ideas&nonce=' . wp_create_nonce('igny8_import_export_nonce')); ?>" class="igny8-btn igny8-btn-outline">
<span class="dashicons dashicons-download"></span> Ideas Template
</a>
</div>
</div>
<!-- Import Form -->
<form id="igny8-import-form" enctype="multipart/form-data" action="<?php echo admin_url('admin-ajax.php'); ?>" method="post">
<input type="hidden" name="action" value="igny8_run_import">
<input type="hidden" name="nonce" value="<?php echo wp_create_nonce('igny8_import_export_nonce'); ?>">
<div class="igny8-form-group">
<label for="import-file">Select CSV File</label>
<input type="file" id="import-file" name="import_file" accept=".csv" required>
<p class="description">Upload a CSV file with your data. Use the templates above for proper format.</p>
</div>
<div class="igny8-form-group">
<label for="import-type">Import Type</label>
<select id="import-type" name="import_type" required>
<option value="">Select import type...</option>
<option value="keywords">Keywords</option>
<option value="clusters">Clusters</option>
<option value="ideas">Content Ideas</option>
</select>
</div>
<div class="igny8-form-actions">
<button type="submit" class="igny8-btn igny8-btn-success">
<span class="dashicons dashicons-upload"></span> Run Import
</button>
</div>
</form>
<!-- Import Results -->
<div id="import-results" class="igny8-mt-20" style="display: none;"></div>
</div>
</div>
<!-- Export Data Section -->
<div class="igny8-card igny8-mb-20">
<div class="igny8-card-header igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Export Data</h3>
<div class="igny8-card-subtitle">Export your data in various formats for backup and migration</div>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-download igny8-dashboard-icon-sm"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<form id="igny8-export-form" action="<?php echo admin_url('admin-ajax.php'); ?>" method="post">
<input type="hidden" name="action" value="igny8_run_export">
<input type="hidden" name="nonce" value="<?php echo wp_create_nonce('igny8_import_export_nonce'); ?>">
<div class="igny8-form-group">
<label for="export-type">Export Type</label>
<select id="export-type" name="export_type" required>
<option value="">Select export type...</option>
<option value="keywords">Keywords</option>
<option value="clusters">Clusters</option>
<option value="ideas">Content Ideas</option>
</select>
</div>
<div class="igny8-form-group">
<h4>Export Options</h4>
<div class="igny8-checkbox-group">
<label class="igny8-checkbox-label">
<input type="checkbox" id="include-metrics" name="include_metrics" <?php checked($import_export_settings['include_metrics'], true); ?>>
<span class="igny8-checkbox-text">Include Metrics</span>
</label>
<label class="igny8-checkbox-label">
<input type="checkbox" id="include-relationships" name="include_relationships">
<span class="igny8-checkbox-text">Include Relationships</span>
</label>
<label class="igny8-checkbox-label">
<input type="checkbox" id="include-timestamps" name="include_timestamps">
<span class="igny8-checkbox-text">Include Timestamps</span>
</label>
</div>
</div>
<div class="igny8-form-actions">
<button type="submit" class="igny8-btn igny8-btn-primary">
<span class="dashicons dashicons-download"></span> Export CSV
</button>
</div>
</form>
<!-- Export Results -->
<div id="export-results" class="igny8-mt-20" style="display: none;"></div>
</div>
</div>
<!-- Settings & Logging Section -->
<div class="igny8-card">
<div class="igny8-card-header igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Import / Export Preferences</h3>
<div class="igny8-card-subtitle">Configure import/export settings and view operation logs</div>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-generic igny8-dashboard-icon-sm"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<form id="igny8-settings-form" action="<?php echo admin_url('admin-ajax.php'); ?>" method="post">
<input type="hidden" name="action" value="igny8_save_import_export_settings">
<input type="hidden" name="nonce" value="<?php echo wp_create_nonce('igny8_import_export_nonce'); ?>">
<div class="igny8-grid-2 igny8-gap-20">
<div class="igny8-form-group">
<label for="default-format">Default Format</label>
<select id="default-format" name="default_format">
<option value="csv" <?php selected($import_export_settings['default_format'], 'csv'); ?>>CSV</option>
<option value="json" <?php selected($import_export_settings['default_format'], 'json'); ?>>JSON</option>
</select>
</div>
<div class="igny8-form-group">
<label for="file-naming">File Naming Pattern</label>
<input type="text" id="file-naming" name="file_naming_pattern" value="<?php echo esc_attr($import_export_settings['file_naming_pattern']); ?>" placeholder="igny8_export_[date].csv">
<p class="description">Use [date], [type], [time] placeholders</p>
</div>
</div>
<div class="igny8-form-group">
<h4>Default Options</h4>
<div class="igny8-checkbox-group">
<label class="igny8-checkbox-label">
<input type="checkbox" id="overwrite-existing" name="overwrite_existing" <?php checked($import_export_settings['overwrite_existing'], true); ?>>
<span class="igny8-checkbox-text">Overwrite Existing Records</span>
</label>
<label class="igny8-checkbox-label">
<input type="checkbox" id="include-metrics-default" name="include_metrics" <?php checked($import_export_settings['include_metrics'], true); ?>>
<span class="igny8-checkbox-text">Include Metrics in Export by Default</span>
</label>
</div>
</div>
<div class="igny8-form-actions">
<button type="submit" class="igny8-btn igny8-btn-success">Save Preferences</button>
</div>
</form>
<!-- Recent Logs -->
<?php if (!empty($recent_logs)): ?>
<div class="igny8-mt-30">
<h4>Recent Import/Export Activity</h4>
<div class="igny8-logs-container" style="max-height: 300px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; padding: 10px; background: #f9f9f9;">
<?php foreach ($recent_logs as $log): ?>
<div class="igny8-log-entry" style="border-bottom: 1px solid #eee; padding: 8px 0; font-size: 12px;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: bold; color: <?php echo $log['status'] === 'success' ? 'green' : 'red'; ?>;">
<?php echo $log['status'] === 'success' ? '✓' : '✗'; ?>
<?php echo esc_html($log['operation']); ?>
</span>
<span style="color: #666;"><?php echo esc_html($log['timestamp']); ?></span>
</div>
<div style="color: #666; margin-top: 2px;">
<?php echo esc_html($log['message']); ?>
<?php if (isset($log['details'])): ?>
<br><small><?php echo esc_html($log['details']); ?></small>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<script>
// Localize script variables
window.IGNY8_IMPORT_EXPORT = {
ajaxUrl: '<?php echo admin_url('admin-ajax.php'); ?>',
nonce: '<?php echo wp_create_nonce('igny8_import_export_nonce'); ?>',
settings: <?php echo json_encode($import_export_settings); ?>
};
document.addEventListener('DOMContentLoaded', function() {
console.log('Import/Export page loaded');
console.log('IGNY8_IMPORT_EXPORT:', window.IGNY8_IMPORT_EXPORT);
// Initialize Import/Export functionality
if (typeof window.initializeImportExport === 'function') {
console.log('Initializing Import/Export functionality');
window.initializeImportExport();
} else {
console.error('initializeImportExport function not found');
}
});
</script>

View File

@@ -0,0 +1,744 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : integration.php
* @location : /modules/settings/integration.php
* @type : Admin Page
* @scope : Module Only
* @allowed : API integration, connection management, external service settings
* @reusability : Single Use
* @notes : API integration settings page for settings module
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Start output buffering
ob_start();
?>
<!-- API Integration Section -->
<div class="igny8-dashboard-section">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>API Integration</h3>
<p class="igny8-card-subtitle">Configure external API connections and integrations</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-site igny8-dashboard-icon-lg igny8-dashboard-icon-blue"></span>
</div>
</div>
</div>
<!-- API Services Grid -->
<div class="igny8-grid-2 igny8-dashboard-section" style="display: flex; flex-direction: row;">
<!-- OpenAI API Card -->
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>OpenAI API</h3>
<p class="igny8-card-subtitle">AI-powered content generation and analysis</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-site igny8-dashboard-icon-lg igny8-dashboard-icon-green"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<form method="post" action="options.php">
<?php settings_fields('igny8_api_settings'); ?>
<table class="form-table">
<tr>
<th scope="row">
<label for="igny8_api_key">OpenAI API Key</label>
</th>
<td>
<input type="password" name="igny8_api_key" id="igny8_api_key" value="<?php echo esc_attr(get_option('igny8_api_key', '')); ?>" class="regular-text" />
<p class="description">Your OpenAI API key for DALL-E 3 image generation and text AI features.</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="igny8_runware_api_key">Runware API Key</label>
</th>
<td>
<input type="password" name="igny8_runware_api_key" id="igny8_runware_api_key" value="<?php echo esc_attr(get_option('igny8_runware_api_key', '')); ?>" class="regular-text" />
<p class="description">Your Runware API key for high-quality image generation. <a href="https://runware.com" target="_blank">Get your API key here</a>.</p>
</td>
</tr>
<tr>
<th scope="row">
<label>AI Model</label>
</th>
<td>
<fieldset>
<label style="display: block; margin-bottom: 10px;">
<input type="radio" name="igny8_model" value="gpt-4.1" <?php checked(get_option('igny8_model', 'gpt-4.1'), 'gpt-4.1'); ?> />
<strong>GPT-4.1</strong> — $2.00 / $8.00 per 1M tokens<br>
<span style="color: #666; font-size: 12px;">Content creation, coding, analysis, high-quality content generation</span>
</label>
<label style="display: block; margin-bottom: 10px;">
<input type="radio" name="igny8_model" value="gpt-4o-mini" <?php checked(get_option('igny8_model', 'gpt-4.1'), 'gpt-4o-mini'); ?> />
<strong>GPT-4o mini</strong> — $0.15 / $0.60 per 1M tokens<br>
<span style="color: #666; font-size: 12px;">Bulk tasks, lightweight AI, cost-effective for high-volume operations</span>
</label>
<label style="display: block;">
<input type="radio" name="igny8_model" value="gpt-4o" <?php checked(get_option('igny8_model', 'gpt-4.1'), 'gpt-4o'); ?> />
<strong>GPT-4o</strong> — $2.50 / $10.00 per 1M tokens<br>
<span style="color: #666; font-size: 12px;">Advanced AI for general and multimodal tasks, faster than GPT-4.1</span>
</label>
</fieldset>
<p class="description">Select the AI model to use for content generation. Pricing shown per 1M tokens.</p>
</td>
</tr>
<tr>
<th scope="row">Image Generation Service</th>
<td>
<fieldset>
<label style="display: block; margin-bottom: 10px;">
<input type="radio" name="igny8_image_service" value="openai" <?php checked(get_option('igny8_image_service', 'openai'), 'openai'); ?> />
<strong>OpenAI</strong> — Multiple models available<br>
<span style="color: #666; font-size: 12px;">High-quality image generation with OpenAI's models</span>
</label>
<label style="display: block;">
<input type="radio" name="igny8_image_service" value="runware" <?php checked(get_option('igny8_image_service', 'openai'), 'runware'); ?> />
<strong>Runware</strong> — $0.036 per image<br>
<span style="color: #666; font-size: 12px;">High-quality AI image generation with Runware's models</span>
</label>
</fieldset>
<p class="description">Select the image generation service to use. Each service requires its own API key.</p>
</td>
</tr>
<tr id="igny8-openai-models-row" style="display: none;">
<th scope="row">OpenAI Image Model</th>
<td>
<fieldset>
<label style="display: block; margin-bottom: 10px;">
<input type="radio" name="igny8_image_model" value="dall-e-3" <?php checked(get_option('igny8_image_model', 'dall-e-3'), 'dall-e-3'); ?> />
<strong>DALL·E 3</strong> — $0.040 per image<br>
<span style="color: #666; font-size: 12px;">High-quality image generation with advanced AI capabilities</span>
</label>
<label style="display: block; margin-bottom: 10px;">
<input type="radio" name="igny8_image_model" value="dall-e-2" <?php checked(get_option('igny8_image_model', 'dall-e-3'), 'dall-e-2'); ?> />
<strong>DALL·E 2</strong> — $0.020 per image<br>
<span style="color: #666; font-size: 12px;">Cost-effective image generation with good quality</span>
</label>
<label style="display: block; margin-bottom: 10px;">
<input type="radio" name="igny8_image_model" value="gpt-image-1" <?php checked(get_option('igny8_image_model', 'dall-e-3'), 'gpt-image-1'); ?> />
<strong>GPT Image 1 (Full)</strong> — $0.042 per image<br>
<span style="color: #666; font-size: 12px;">Full-featured image generation with comprehensive capabilities</span>
</label>
<label style="display: block;">
<input type="radio" name="igny8_image_model" value="gpt-image-1-mini" <?php checked(get_option('igny8_image_model', 'dall-e-3'), 'gpt-image-1-mini'); ?> />
<strong>GPT Image 1 Mini</strong> — $0.011 per image<br>
<span style="color: #666; font-size: 12px;">Lightweight, cost-effective image generation for bulk operations</span>
</label>
</fieldset>
<p class="description">Select the OpenAI image model to use. Pricing shown per image.</p>
</td>
</tr>
<tr id="igny8-runware-models-row" style="display: none;">
<th scope="row">Runware Image Model</th>
<td>
<fieldset>
<label style="display: block;">
<input type="radio" name="igny8_runware_model" value="runware:97@1" <?php checked(get_option('igny8_runware_model', 'runware:97@1'), 'runware:97@1'); ?> />
<strong>HiDream-I1 Full</strong> — $0.036 per image<br>
<span style="color: #666; font-size: 12px;">High-quality AI image generation with Runware's HiDream model</span>
</label>
</fieldset>
<p class="description">Select the Runware image model to use. Pricing shown per image.</p>
</td>
</tr>
<tr>
<th scope="row">API Validation</th>
<td>
<div id="igny8-openai-validation" style="display: none;">
<button type="button" id="igny8-test-api" class="button">Test OpenAI Connection</button>
<button type="button" id="igny8-test-response" class="button">Test OpenAI Response (Ping)</button>
</div>
<div id="igny8-runware-validation" style="display: none;">
<button type="button" id="igny8-test-runware-btn" class="button">Test Runware Connection</button>
<div id="igny8-runware-test-result" style="margin-top: 10px;"></div>
</div>
<div id="igny8-api-test-result" style="margin-top: 10px;"></div>
</td>
</tr>
</table>
<?php submit_button('Save API Settings'); ?>
</form>
</div>
</div>
<!-- Google Search Console API Card -->
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Google Search Console API</h3>
<p class="igny8-card-subtitle">Search performance and ranking data integration</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-chart-bar igny8-dashboard-icon-lg igny8-dashboard-icon-orange"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<div style="background: rgba(255, 193, 7, 0.1); border: 1px solid var(--amber); border-radius: var(--radius); padding: 16px; margin-bottom: 20px;">
<div style="display: flex; align-items: center; gap: 12px;">
<span style="font-size: 24px;">⚠️</span>
<div>
<strong style="color: var(--amber-dark);">Coming Soon</strong><br>
<span style="color: var(--text-dim); font-size: 14px;">
Google Search Console API integration is currently in development.
This will provide search performance data and ranking insights.
</span>
</div>
</div>
</div>
<div style="color: var(--text-dim); margin-bottom: 16px;">
<strong>Planned Features:</strong>
<ul style="margin: 8px 0; padding-left: 20px;">
<li>Search performance metrics</li>
<li>Keyword ranking data</li>
<li>Click-through rate analysis</li>
<li>Search appearance insights</li>
</ul>
</div>
<div style="text-align: center; padding: 20px; background: var(--bg-light); border-radius: var(--radius);">
<span style="color: var(--text-dim); font-size: 14px;">
Integration will be available in a future update
</span>
</div>
</div>
</div>
</div>
</div>
<!-- API Request Logs Section -->
<div class="igny8-dashboard-section">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>API Request Logs</h3>
<p class="igny8-card-subtitle">Monitor API usage and performance metrics</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-chart-line igny8-dashboard-icon-lg igny8-dashboard-icon-purple"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div id="igny8-api-logs-container">
<div class="tablenav top">
<div class="alignleft actions">
<button type="button" id="igny8-refresh-logs" class="button">Refresh Logs</button>
<button type="button" id="igny8-clear-logs" class="button">Clear Logs</button>
</div>
<div class="alignright actions">
<span class="displaying-num">
<span id="igny8-logs-count">0</span> API calls
</span>
</div>
</div>
<table class="widefat fixed striped" id="igny8-api-logs">
<thead>
<tr>
<th>Timestamp</th>
<th>Status</th>
<th>Model</th>
<th>Tokens (In/Out)</th>
<th>Cost</th>
<th>API ID</th>
</tr>
</thead>
<tbody>
<?php
global $wpdb;
$logs = $wpdb->get_results("
SELECT * FROM {$wpdb->prefix}igny8_logs
WHERE source = 'openai_api'
ORDER BY created_at DESC
LIMIT 20
");
if ($logs) {
foreach ($logs as $log) {
$context = json_decode($log->context, true);
$status_class = $log->status === 'success' ? 'success' : 'error';
$status_icon = $log->status === 'success' ? '✅' : '❌';
// Debug logging for cost display
error_log("Igny8 Display Debug: Log ID=" . $log->id . ", total_cost=" . ($context['total_cost'] ?? 'null') . ", formatted=" . igny8_format_cost($context['total_cost'] ?? 0));
echo "<tr>
<td>" . esc_html($log->created_at) . "</td>
<td><span class='igny8-status {$status_class}'>{$status_icon} " . esc_html($log->status) . "</span></td>
<td>" . esc_html($context['model'] ?? 'Unknown') . "</td>
<td>" . intval($context['input_tokens'] ?? 0) . " / " . intval($context['output_tokens'] ?? 0) . "</td>
<td>" . igny8_format_cost($context['total_cost'] ?? 0) . "</td>
<td>" . esc_html($log->api_id ? substr($log->api_id, 0, 12) . '...' : 'N/A') . "</td>
</tr>";
}
} else {
echo '<tr><td colspan="6">No API logs found.</td></tr>';
}
?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Image Request Logs Section -->
<div class="igny8-dashboard-section">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Image Request Logs</h3>
<p class="igny8-card-subtitle">Monitor AI image generation requests and performance</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-format-image igny8-dashboard-icon-lg igny8-dashboard-icon-teal"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div id="igny8-image-logs-container">
<div class="tablenav top">
<div class="alignleft actions">
<button type="button" id="igny8-refresh-image-logs" class="button">Refresh Logs</button>
<button type="button" id="igny8-clear-image-logs" class="button">Clear Logs</button>
</div>
<div class="alignright actions">
<span class="displaying-num">
<span id="igny8-image-logs-count">0</span> image requests
</span>
</div>
</div>
<table class="widefat fixed striped" id="igny8-image-logs">
<thead>
<tr>
<th>Timestamp</th>
<th>Status</th>
<th>Model</th>
<th>Prompt Length</th>
<th>Cost</th>
<th>Image Size</th>
<th>API ID</th>
</tr>
</thead>
<tbody>
<?php
$image_logs = $wpdb->get_results("
SELECT * FROM {$wpdb->prefix}igny8_logs
WHERE source = 'openai_image'
ORDER BY created_at DESC
LIMIT 20
");
if ($image_logs) {
foreach ($image_logs as $log) {
$context = json_decode($log->context, true);
$status_class = $log->status === 'success' ? 'success' : 'error';
$status_icon = $log->status === 'success' ? '✅' : '❌';
echo "<tr>
<td>" . esc_html($log->created_at) . "</td>
<td><span class='igny8-status {$status_class}'>{$status_icon} " . esc_html($log->status) . "</span></td>
<td>" . esc_html($context['model'] ?? 'dall-e-3') . "</td>
<td>" . intval($context['prompt_length'] ?? 0) . " chars</td>
<td>" . igny8_format_cost($context['total_cost'] ?? 0) . "</td>
<td>" . esc_html($context['image_size'] ?? '1024x1024') . "</td>
<td>" . esc_html($log->api_id ? substr($log->api_id, 0, 12) . '...' : 'N/A') . "</td>
</tr>";
}
} else {
echo '<tr><td colspan="7">No image request logs found.</td></tr>';
}
?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Content Engine Settings Section -->
<div class="igny8-dashboard-section">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Content Engine Settings</h3>
<p class="igny8-card-subtitle">Personalization and content generation configuration</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-generic igny8-dashboard-icon-lg igny8-dashboard-icon-amber"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div style="background: rgba(59, 130, 246, 0.1); border: 1px solid var(--blue); border-radius: var(--radius); padding: 16px; margin-bottom: 20px;">
<div style="display: flex; align-items: center; gap: 12px;">
<span style="font-size: 24px;"></span>
<div>
<strong style="color: var(--blue-dark);">Settings Moved</strong><br>
<span style="color: var(--text-dim); font-size: 14px;">
Content Engine settings have been moved to the Personalize module for better organization.
</span>
</div>
</div>
</div>
<p><strong>Note:</strong> Content Engine settings have been moved to the <a href="<?php echo admin_url('admin.php?page=igny8-personalize&sm=settings'); ?>">Personalize module</a> for better organization.</p>
<p>Please configure personalization settings in the Personalize → Settings section.</p>
</div>
</div>
</div>
<!-- Third-party Integrations Section -->
<div class="igny8-dashboard-section">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Third-party Integrations</h3>
<p class="igny8-card-subtitle">Additional SEO tools and data sources</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-tools igny8-dashboard-icon-lg igny8-dashboard-icon-red"></span>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-card-body">
<div style="background: rgba(255, 193, 7, 0.1); border: 1px solid var(--amber); border-radius: var(--radius); padding: 16px; margin-bottom: 20px;">
<div style="display: flex; align-items: center; gap: 12px;">
<span style="font-size: 24px;">⚠️</span>
<div>
<strong style="color: var(--amber-dark);">Coming Soon</strong><br>
<span style="color: var(--text-dim); font-size: 14px;">
Additional third-party integrations are currently in development.
</span>
</div>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div style="padding: 16px; background: var(--bg-light); border-radius: var(--radius);">
<h4 style="margin: 0 0 8px 0; color: var(--text-primary);">Ahrefs API</h4>
<p style="margin: 0; color: var(--text-dim); font-size: 14px;">Integration with Ahrefs for keyword and backlink data.</p>
</div>
<div style="padding: 16px; background: var(--bg-light); border-radius: var(--radius);">
<h4 style="margin: 0 0 8px 0; color: var(--text-primary);">SEMrush API</h4>
<p style="margin: 0; color: var(--text-dim); font-size: 14px;">Integration with SEMrush for competitive analysis.</p>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Handle conditional display of image model options and API validation
const imageServiceRadios = document.querySelectorAll('input[name="igny8_image_service"]');
const openaiModelsRow = document.getElementById('igny8-openai-models-row');
const runwareModelsRow = document.getElementById('igny8-runware-models-row');
const openaiValidation = document.getElementById('igny8-openai-validation');
const runwareValidation = document.getElementById('igny8-runware-validation');
function toggleServiceOptions() {
const selectedService = document.querySelector('input[name="igny8_image_service"]:checked');
if (selectedService) {
if (selectedService.value === 'openai') {
openaiModelsRow.style.display = 'table-row';
runwareModelsRow.style.display = 'none';
openaiValidation.style.display = 'block';
runwareValidation.style.display = 'none';
} else if (selectedService.value === 'runware') {
openaiModelsRow.style.display = 'none';
runwareModelsRow.style.display = 'table-row';
openaiValidation.style.display = 'none';
runwareValidation.style.display = 'block';
}
}
}
// Add event listeners to image service radio buttons
imageServiceRadios.forEach(radio => {
radio.addEventListener('change', toggleServiceOptions);
});
// Initialize display on page load
toggleServiceOptions();
const testApiButton = document.getElementById('igny8-test-api');
const testResponseButton = document.getElementById('igny8-test-response');
const refreshLogsButton = document.getElementById('igny8-refresh-logs');
const clearLogsButton = document.getElementById('igny8-clear-logs');
const refreshImageLogsButton = document.getElementById('igny8-refresh-image-logs');
const clearImageLogsButton = document.getElementById('igny8-clear-image-logs');
const resultDiv = document.getElementById('igny8-api-test-result');
// Test API Connection (without I/O messages)
if (testApiButton) {
testApiButton.addEventListener('click', function() {
testApiConnection(false);
});
}
// Test API Response (with "test ping")
if (testResponseButton) {
testResponseButton.addEventListener('click', function() {
testApiConnection(true);
});
}
// Refresh Logs
if (refreshLogsButton) {
refreshLogsButton.addEventListener('click', function() {
loadApiLogs();
});
}
// Clear Logs
if (clearLogsButton) {
clearLogsButton.addEventListener('click', function() {
clearApiLogs();
});
}
// Refresh Image Logs
if (refreshImageLogsButton) {
refreshImageLogsButton.addEventListener('click', function() {
loadImageLogs();
});
}
// Clear Image Logs
if (clearImageLogsButton) {
clearImageLogsButton.addEventListener('click', function() {
clearImageLogs();
});
}
function testApiConnection(withResponse = false) {
resultDiv.innerHTML = '<span style="color: blue;">Testing API connection...</span>';
const formData = new FormData();
formData.append('action', 'igny8_test_api');
formData.append('nonce', '<?php echo wp_create_nonce('igny8_ajax_nonce'); ?>');
formData.append('with_response', withResponse ? '1' : '0');
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
const modelUsed = data.data?.model_used || 'Unknown';
const response = data.data?.response || 'No response';
const cost = data.data?.cost || 'N/A';
const tokens = data.data?.tokens || 'N/A';
const tokensSent = data.data?.tokens_sent || 'N/A';
const tokensUsed = data.data?.tokens_used || 'N/A';
const totalTokens = data.data?.total_tokens || 'N/A';
resultDiv.innerHTML = `
<div style="color: green; margin-bottom: 10px;">
✓ API connection successful!
</div>
<div style="background: #f0f8ff; padding: 10px; border-radius: 4px; border-left: 4px solid #0073aa;">
<strong>Model Used:</strong> ${modelUsed}<br>
${withResponse ? `<strong>Expected:</strong> "OK! Ping Received"<br><strong>Actual Response:</strong> "${response}"<br>` : ''}
<strong>Token Limit Sent:</strong> ${tokensSent} (from your settings)<br>
<strong>Tokens Used:</strong> ${tokensUsed} (input/output)<br>
<strong>Total Tokens:</strong> ${totalTokens}<br>
<strong>Cost:</strong> ${cost}
</div>
`;
// Refresh logs to show new entry
setTimeout(() => loadApiLogs(), 1000);
} else {
const errorMessage = data.data?.message || 'Unknown error';
const errorDetails = data.data?.details || '';
resultDiv.innerHTML = `
<div style="color: red; margin-bottom: 10px;">
✗ API connection failed: ${errorMessage}
</div>
${errorDetails ? `<div style="background: #fff2f2; padding: 10px; border-radius: 4px; border-left: 4px solid #dc3232; font-size: 12px; color: #666;">
<strong>Details:</strong> ${errorDetails}
</div>` : ''}
`;
}
})
.catch(error => {
resultDiv.innerHTML = '<span style="color: red;">✗ API connection failed: ' + error.message + '</span>';
});
}
function loadApiLogs() {
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
action: 'igny8_get_api_logs',
nonce: '<?php echo wp_create_nonce('igny8_ajax_nonce'); ?>'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
const logsTable = document.querySelector('#igny8-api-logs tbody');
const logsCount = document.getElementById('igny8-logs-count');
if (logsTable) {
logsTable.innerHTML = data.data.html;
}
if (logsCount) {
logsCount.textContent = data.data.total;
}
}
})
.catch(error => {
console.error('Error loading logs:', error);
});
}
function clearApiLogs() {
if (!confirm('Are you sure you want to clear all API logs? This action cannot be undone.')) {
return;
}
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
action: 'igny8_clear_api_logs',
nonce: '<?php echo wp_create_nonce('igny8_ajax_nonce'); ?>'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
loadApiLogs();
alert('API logs cleared successfully!');
} else {
alert('Error clearing logs: ' + (data.data?.message || 'Unknown error'));
}
})
.catch(error => {
console.error('Error clearing logs:', error);
alert('Error clearing logs: ' + error.message);
});
}
function loadImageLogs() {
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
action: 'igny8_get_image_logs',
nonce: '<?php echo wp_create_nonce('igny8_ajax_nonce'); ?>'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
const logsTable = document.querySelector('#igny8-image-logs tbody');
const logsCount = document.getElementById('igny8-image-logs-count');
if (logsTable) {
logsTable.innerHTML = data.data.html;
}
if (logsCount) {
logsCount.textContent = data.data.total;
}
}
})
.catch(error => {
console.error('Error loading image logs:', error);
});
}
function clearImageLogs() {
if (!confirm('Are you sure you want to clear all image request logs? This action cannot be undone.')) {
return;
}
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
action: 'igny8_clear_image_logs',
nonce: '<?php echo wp_create_nonce('igny8_ajax_nonce'); ?>'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
loadImageLogs();
alert('Image request logs cleared successfully!');
} else {
alert('Error clearing image logs: ' + (data.data?.message || 'Unknown error'));
}
})
.catch(error => {
console.error('Error clearing image logs:', error);
alert('Error clearing image logs: ' + error.message);
});
}
// Load logs on page load
loadApiLogs();
loadImageLogs();
});
</script>
<style>
.igny8-status.success {
color: #46b450;
font-weight: bold;
}
.igny8-status.error {
color: #dc3232;
font-weight: bold;
}
#igny8-api-logs {
margin-top: 15px;
}
#igny8-api-logs th {
background: #f1f1f1;
font-weight: bold;
}
#igny8-api-logs td {
padding: 8px 10px;
}
</style>

View File

@@ -0,0 +1,297 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : schedules.php
* @location : /modules/settings/schedules.php
* @type : Admin Page
* @scope : Module Only
* @allowed : Automation scheduling, cron configuration, timing settings
* @reusability : Single Use
* @notes : Automation schedules configuration page for settings module
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Start output buffering
ob_start();
// Load master dispatcher functions
if (!function_exists('igny8_get_defined_cron_jobs')) {
include_once plugin_dir_path(__FILE__) . '../../core/cron/igny8-cron-master-dispatcher.php';
}
// Handle form submission
if (isset($_POST['igny8_save_cron_settings']) && wp_verify_nonce($_POST['igny8_cron_nonce'], 'igny8_cron_settings')) {
$cron_jobs = $_POST['cron_jobs'] ?? [];
$cron_settings = get_option('igny8_cron_settings', []);
$cron_limits = get_option('igny8_cron_limits', []);
// Update settings for each job
foreach ($cron_jobs as $job_name => $job_data) {
$cron_settings[$job_name] = [
'enabled' => isset($job_data['enabled']),
'last_run' => $cron_settings[$job_name]['last_run'] ?? 0
];
// Update limits
if (isset($job_data['limit'])) {
$cron_limits[$job_name] = intval($job_data['limit']);
}
}
update_option('igny8_cron_settings', $cron_settings);
update_option('igny8_cron_limits', $cron_limits);
echo '<div class="notice notice-success"><p>Settings saved successfully!</p></div>';
}
// Get current data
$defined_jobs = igny8_get_defined_cron_jobs();
$cron_settings = get_option('igny8_cron_settings', []);
$cron_limits = get_option('igny8_cron_limits', []);
$last_execution = get_option('igny8_cron_last_execution', []);
// Initialize defaults if needed
if (empty($cron_settings)) {
$cron_settings = igny8_get_default_cron_settings();
update_option('igny8_cron_settings', $cron_settings);
}
if (empty($cron_limits)) {
$cron_limits = igny8_get_default_cron_limits();
update_option('igny8_cron_limits', $cron_limits);
}
// Get health status for all jobs
$health_status = [];
foreach ($defined_jobs as $job_name => $job_config) {
$health_status[$job_name] = igny8_get_job_health_status($job_name);
}
?>
<div class="wrap igny8-admin-page">
<!-- Smart Automation Jobs Table -->
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Smart Automation Jobs</h3>
<p class="igny8-card-subtitle">Configure and manage all automation jobs</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-tools igny8-dashboard-icon-lg igny8-dashboard-icon-purple"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<form method="post" action="">
<?php wp_nonce_field('igny8_cron_settings', 'igny8_cron_nonce'); ?>
<table class="igny8-table" data-cron-key="<?php echo esc_attr(get_option('igny8_secure_cron_key')); ?>">
<thead>
<tr>
<th>Job Name</th>
<th>Module</th>
<th>Enable</th>
<th>Max Items</th>
<th>Last Run</th>
<th>Execution Time</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($defined_jobs as $job_name => $job_config):
// Skip crons if their respective modules are disabled
$analytics_crons = ['igny8_process_ai_queue_cron', 'igny8_auto_recalc_cron', 'igny8_health_check_cron'];
$writer_crons = ['igny8_auto_generate_content_cron', 'igny8_auto_generate_images_cron', 'igny8_auto_publish_drafts_cron'];
$optimizer_crons = ['igny8_auto_optimizer_cron'];
if (in_array($job_name, $analytics_crons) && !igny8_is_module_enabled('analytics')) {
continue;
}
if (in_array($job_name, $writer_crons) && !igny8_is_module_enabled('writer')) {
continue;
}
if (in_array($job_name, $optimizer_crons) && !igny8_is_module_enabled('optimizer')) {
continue;
}
$job_settings = $cron_settings[$job_name] ?? [];
$job_status = igny8_get_cron_job_status($job_name);
$job_health = $health_status[$job_name];
?>
<tr>
<td>
<strong><?php echo esc_html($job_config['description']); ?></strong>
</td>
<td>
<span class="igny8-module-badge igny8-module-<?php echo esc_attr($job_config['module']); ?>">
<?php echo esc_html(ucfirst($job_config['module'])); ?>
</span>
</td>
<td>
<label class="igny8-toggle">
<input type="checkbox" name="cron_jobs[<?php echo esc_attr($job_name); ?>][enabled]"
<?php checked($job_settings['enabled'] ?? false); ?>>
<span class="igny8-toggle-slider"></span>
</label>
</td>
<td>
<input type="number" name="cron_jobs[<?php echo esc_attr($job_name); ?>][limit]"
value="<?php echo esc_attr($cron_limits[$job_name] ?? 10); ?>"
min="1" max="100" style="width: 60px;">
<?php
// Add descriptive text based on job type
$item_text = '';
switch($job_name) {
case 'igny8_auto_cluster_cron':
$item_text = 'keywords';
break;
case 'igny8_auto_generate_ideas_cron':
$item_text = 'clusters';
break;
case 'igny8_auto_queue_cron':
$item_text = 'ideas';
break;
case 'igny8_auto_generate_content_cron':
$item_text = 'tasks';
break;
case 'igny8_auto_generate_images_cron':
$item_text = 'posts';
break;
case 'igny8_auto_publish_drafts_cron':
$item_text = 'drafts';
break;
case 'igny8_process_ai_queue_cron':
$item_text = 'tasks';
break;
case 'igny8_auto_recalc_cron':
$item_text = 'items';
break;
default:
$item_text = 'items';
}
echo ' <small style="color: #666; font-size: 12px;">' . $item_text . '</small>';
?>
</td>
<td>
<?php echo esc_html($job_health['last_run']); ?>
<?php if (!empty($job_health['result_details'])): ?>
<br><small style="color: <?php echo ($job_health['last_success'] === true) ? '#28a745' : '#dc3545'; ?>;">
<?php echo esc_html($job_health['result_details']); ?>
<?php if ($job_health['last_success'] === true): ?>
<?php else: ?>
<?php endif; ?>
</small>
<?php endif; ?>
</td>
<td>
<?php
$execution_time = $job_health['execution_time'] ?? 0;
echo $execution_time > 0 ? number_format($execution_time, 2) . 's' : 'N/A';
?>
<?php if (!empty($job_health['execution_method'])): ?>
<br><small style="color: #666;">
via <?php echo esc_html($job_health['execution_method']); ?>
</small>
<?php endif; ?>
</td>
<td class="igny8-align-center igny8-actions">
<button class="igny8-icon-only igny8-icon-external" data-action="openInNewWindow" data-hook="<?php echo esc_attr($job_name); ?>" title="Open in New Window">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
<polyline points="15,3 21,3 21,9"></polyline>
<line x1="10" y1="14" x2="21" y2="3"></line>
</svg>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<p class="submit">
<input type="submit" name="igny8_save_cron_settings" class="button-primary" value="Save All Settings">
</p>
</form>
</div>
</div>
<!-- Master Scheduler Status -->
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Master Scheduler Configuration</h3>
<p class="igny8-card-subtitle">Single cron job manages all automation</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-clock igny8-dashboard-icon-lg igny8-dashboard-icon-blue"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<p><strong>Single cPanel Configuration Required:</strong></p>
<code>*/5 * * * * curl -s "https://<?php echo $_SERVER['HTTP_HOST']; ?>/wp-load.php?import_key=<?php echo get_option('igny8_secure_cron_key'); ?>&import_id=igny8_cron&action=master_scheduler" > /dev/null 2>&1</code>
<p><em>This single cron job will intelligently manage all automation based on your settings below.</em></p>
</div>
</div>
<!-- System Overview -->
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>System Overview</h3>
<p class="igny8-card-subtitle">Current automation system status</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-dashboard igny8-dashboard-icon-lg igny8-dashboard-icon-green"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<?php
$total_jobs = count($defined_jobs);
$enabled_jobs = count(array_filter($cron_settings, function($settings) { return $settings['enabled'] ?? false; }));
$scheduled_jobs = count(array_filter($health_status, function($status) { return $status['enabled']; }));
$failed_jobs = count(array_filter($health_status, function($status) { return $status['last_success'] === false; }));
?>
<div class="igny8-stats-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 20px; margin: 20px 0;">
<div class="igny8-metric-item" style="background: var(--panel); border: 1px solid var(--stroke); border-radius: var(--radius); padding: 15px; text-align: center;">
<h4 style="margin: 0 0 10px 0; color: var(--blue-dark);">Total Jobs</h4>
<p style="margin: 0; font-size: 24px; font-weight: 700; color: var(--text);"><?php echo esc_html($total_jobs); ?></p>
</div>
<div class="igny8-metric-item" style="background: var(--panel); border: 1px solid var(--stroke); border-radius: var(--radius); padding: 15px; text-align: center;">
<h4 style="margin: 0 0 10px 0; color: var(--green-dark);">Enabled Jobs</h4>
<p style="margin: 0; font-size: 24px; font-weight: 700; color: var(--text);"><?php echo esc_html($enabled_jobs); ?></p>
</div>
<div class="igny8-metric-item" style="background: var(--panel); border: 1px solid var(--stroke); border-radius: var(--radius); padding: 15px; text-align: center;">
<h4 style="margin: 0 0 10px 0; color: var(--amber-dark);">Scheduled Jobs</h4>
<p style="margin: 0; font-size: 24px; font-weight: 700; color: var(--text);"><?php echo esc_html($scheduled_jobs); ?></p>
</div>
<div class="igny8-metric-item" style="background: var(--panel); border: 1px solid var(--stroke); border-radius: var(--radius); padding: 15px; text-align: center;">
<h4 style="margin: 0 0 10px 0; color: var(--red-dark);">Failed Jobs</h4>
<p style="margin: 0; font-size: 24px; font-weight: 700; color: var(--text);"><?php echo esc_html($failed_jobs); ?></p>
</div>
</div>
</div>
</div>
</div>
<?php
// Capture page content
$igny8_page_content = ob_get_clean();
// Include global layout
include plugin_dir_path(__FILE__) . '../../core/global-layout.php';
?>

View File

@@ -0,0 +1,353 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : status.php
* @location : /modules/settings/status.php
* @type : Admin Page
* @scope : Module Only
* @allowed : System status monitoring, health checks, diagnostics
* @reusability : Single Use
* @notes : System status monitoring page for settings module
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
?>
<!-- System Status Section -->
<div class="igny8-dashboard-section">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>System Status</h3>
<p class="igny8-card-subtitle">Monitor system health and component status</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-site igny8-dashboard-icon-lg igny8-dashboard-icon-blue"></span>
</div>
</div>
</div>
<!-- Debug Monitoring & System Layers Section -->
<div class="igny8-grid-2 igny8-dashboard-section" style="
display: flex;
flex-direction: row;
">
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Debug Monitoring</h3>
<p class="igny8-card-subtitle">Real-time debug monitoring controls and status</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-tools igny8-dashboard-icon-lg igny8-dashboard-icon-orange"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<p style="color: var(--text-dim); margin-bottom: 16px;">Enable or disable submodule debug monitoring for Planner, Writer, Linker, Optimizer and Personalize</p>
<!-- Real-time Monitoring Controls -->
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 20px;">
<div style="font-size: 13px; color: var(--text-dim); text-align: right;">
Real-time Monitoring
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<div style="display: flex; align-items: center; gap: 4px;">
<input type="radio" id="debug-enabled" name="debug_monitoring" value="1" <?php echo get_option('igny8_debug_enabled', false) ? 'checked' : ''; ?>>
<label for="debug-enabled" style="font-size: 12px; color: var(--text-dim);">On</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="radio" id="debug-disabled" name="debug_monitoring" value="0" <?php echo !get_option('igny8_debug_enabled', false) ? 'checked' : ''; ?>>
<label for="debug-disabled" style="font-size: 12px; color: var(--text-dim);">Off</label>
</div>
<button type="button" id="save-debug-setting" class="igny8-btn igny8-btn-primary">
<span class="dashicons dashicons-yes-alt" style="font-size: 14px;"></span>
Save
</button>
</div>
</div>
<!-- Current Status Display -->
<?php
$debug_enabled = get_option('igny8_debug_enabled', false);
$status_text = $debug_enabled ? 'Enabled' : 'Disabled';
$status_color = $debug_enabled ? 'var(--green-dark)' : 'var(--red-dark)';
$status_icon = $debug_enabled ? '✅' : '❌';
// Set background and border colors based on status
if ($debug_enabled) {
$status_bg = 'rgba(16,185,129,0.1)';
$status_border = 'var(--green)';
} else {
$status_bg = 'rgba(239,68,68,0.1)';
$status_border = 'var(--red)';
}
?>
<div style="background: <?php echo $status_bg; ?>; border: 1px solid <?php echo $status_border; ?>; border-radius: var(--radius); padding: 16px; margin-top: 16px;">
<div style="display: flex; align-items: center; gap: 12px;">
<span style="font-size: 24px;"><?php echo $status_icon; ?></span>
<div>
<strong style="color: <?php echo $status_color; ?>;">Debug Monitoring: <?php echo $status_text; ?></strong><br>
<span style="color: var(--text-dim); font-size: 14px;">
<?php if ($debug_enabled): ?>
Real-time debug monitoring is active for all submodules. Debug cards and status indicators are visible on submodule pages.
<?php else: ?>
Debug monitoring is disabled. Debug cards and status indicators are hidden on submodule pages.
<?php endif; ?>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>System Layers</h3>
<p class="igny8-card-subtitle">System layer health and operational metrics</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-chart-bar igny8-dashboard-icon-lg igny8-dashboard-icon-teal"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<!-- Metrics Row -->
<div class="igny8-metrics-row">
<div class="igny8-metric">
<div class="igny8-metric-value">100%</div>
<div class="igny8-metric-label">Layer Health</div>
</div>
<div class="igny8-metric">
<div class="igny8-metric-value" style="color: var(--green);">6</div>
<div class="igny8-metric-label">Operational</div>
</div>
<div class="igny8-metric">
<div class="igny8-metric-value" style="color: var(--red-dark);">0</div>
<div class="igny8-metric-label">Failed</div>
</div>
<div class="igny8-metric">
<div class="igny8-metric-value">6</div>
<div class="igny8-metric-label">Total Layers</div>
</div>
</div>
<!-- Layer Status Circles -->
<div class="igny8-flex" style="justify-content: center; gap: 16px; margin: 20px 0;">
<?php
$layer_labels = [
'database_system' => 'DB',
'configuration_system' => 'CFG',
'rendering_system' => 'RND',
'javascript_ajax' => 'JS',
'component_functionality' => 'CMP',
'data_flow' => 'FLW'
];
foreach ($layer_labels as $layer_key => $label):
$layer_status = true; // Simplified - always true for basic layer summary
$status_class = $layer_status ? 'bg-success' : 'bg-error';
?>
<div class="bg-circle <?php echo $status_class; ?>" title="<?php echo ucfirst(str_replace('_', ' ', $layer_key)); ?>: <?php echo $layer_status ? 'Operational' : 'Failed'; ?>">
<?php echo $label; ?>
</div>
<?php endforeach; ?>
</div>
<div style="background: rgba(16,185,129,0.1); border: 1px solid var(--green); border-radius: var(--radius); padding: 16px; margin-top: 16px;">
<div style="display: flex; align-items: center; gap: 12px;">
<span style="font-size: 24px;">✅</span>
<div>
<strong style="color: var(--green-dark);">All Layers Operational</strong><br>
<span style="color: var(--text-dim); font-size: 14px;">
All 6 layers are functioning correctly.
The Igny8 plugin is operating at full capacity.
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- System Information & Database Status Section -->
<div class="igny8-dashboard-section">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>System Information & Database Status</h3>
<p class="igny8-card-subtitle">System details, database tables, and module status</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-site igny8-dashboard-icon-lg igny8-dashboard-icon-blue"></span>
</div>
</div>
</div>
<!-- 4 Cards Grid -->
<div class="igny8-grid igny8-grid-4">
<!-- Card 1: System Information & API Status -->
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>System & API Info</h3>
<p class="igny8-card-subtitle">System information and API configuration status</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-site igny8-dashboard-icon-lg igny8-dashboard-icon-blue"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<div style="margin-bottom: 15px;">
<div style="font-weight: bold; margin-bottom: 8px; color: #333;">System Information</div>
<div style="font-size: 12px; margin-bottom: 4px;"><strong>Plugin:</strong> <?php echo get_option('igny8_version', 'Unknown'); ?></div>
<div style="font-size: 12px; margin-bottom: 4px;"><strong>WordPress:</strong> <?php echo get_bloginfo('version'); ?></div>
<div style="font-size: 12px; margin-bottom: 4px;"><strong>PHP:</strong> <?php echo PHP_VERSION; ?></div>
<div style="font-size: 12px; margin-bottom: 4px;"><strong>MySQL:</strong> <?php global $wpdb; echo $wpdb->db_version(); ?></div>
</div>
<div>
<div style="font-weight: bold; margin-bottom: 8px; color: #333;">API Status</div>
<?php
$api_key = get_option('igny8_api_key');
$api_configured = !empty($api_key);
$api_status_color = $api_configured ? 'green' : 'red';
$api_status_icon = $api_configured ? '✓' : '✗';
?>
<div style="font-size: 12px; margin-bottom: 4px; color: <?php echo $api_status_color; ?>;">
<strong>OpenAI API:</strong> <?php echo $api_status_icon; ?> <?php echo $api_configured ? 'Configured' : 'Not Configured'; ?>
</div>
<div style="font-size: 12px; margin-bottom: 4px;"><strong>Model:</strong> <?php echo get_option('igny8_model', 'gpt-4.1'); ?></div>
</div>
</div>
</div>
<!-- Card 2: Database Tables Part 1 -->
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Database Tables 1</h3>
<p class="igny8-card-subtitle">Core database tables status and health</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-database igny8-dashboard-icon-lg igny8-dashboard-icon-green"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<?php
global $wpdb;
$tables_part1 = [
'igny8_keywords' => 'Keywords',
'igny8_tasks' => 'Tasks',
'igny8_data' => 'Data',
'igny8_variations' => 'Variations',
'igny8_rankings' => 'Rankings',
'igny8_suggestions' => 'Suggestions',
'igny8_campaigns' => 'Campaigns'
];
foreach ($tables_part1 as $table => $name) {
$table_name = $wpdb->prefix . $table;
$exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") == $table_name;
$status_color = $exists ? 'green' : 'red';
$status_icon = $exists ? '✓' : '✗';
echo "<div style='font-size: 12px; margin-bottom: 3px; color: $status_color;'>$status_icon $name</div>";
}
?>
</div>
</div>
<!-- Card 3: Database Tables Part 2 -->
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Database Tables 2</h3>
<p class="igny8-card-subtitle">Extended database tables status and health</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-database igny8-dashboard-icon-lg igny8-dashboard-icon-purple"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<?php
$tables_part2 = [
'igny8_content_ideas' => 'Content Ideas',
'igny8_clusters' => 'Clusters',
'igny8_sites' => 'Sites',
'igny8_backlinks' => 'Backlinks',
'igny8_mapping' => 'Mapping',
'igny8_prompts' => 'Prompts',
'igny8_logs' => 'Logs'
];
foreach ($tables_part2 as $table => $name) {
$table_name = $wpdb->prefix . $table;
$exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") == $table_name;
$status_color = $exists ? 'green' : 'red';
$status_icon = $exists ? '✓' : '✗';
echo "<div style='font-size: 12px; margin-bottom: 3px; color: $status_color;'>$status_icon $name</div>";
}
?>
</div>
</div>
<!-- Card 4: Module Status -->
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Module Status</h3>
<p class="igny8-card-subtitle">Plugin modules activation and status</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-tools igny8-dashboard-icon-lg igny8-dashboard-icon-amber"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<?php
$modules = [
'planner' => 'Planner',
'writer' => 'Writer',
'personalize' => 'Personalize',
'optimizer' => 'Optimizer',
'linker' => 'Linker'
];
foreach ($modules as $module => $name) {
$enabled = get_option("igny8_{$module}_enabled", true);
$status_color = $enabled ? 'green' : 'red';
$status_icon = $enabled ? '✓' : '✗';
echo "<div style='font-size: 12px; margin-bottom: 3px; color: $status_color;'>$status_icon $name</div>";
}
?>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
if (typeof window.initializeDebugToggle === 'function') {
window.initializeDebugToggle();
}
});
</script>

View File

@@ -0,0 +1,917 @@
<?php
/**
* ==========================
* 🔐 IGNY8 FILE RULE HEADER
* ==========================
* @file : image-testing.php
* @location : /modules/thinker/image-testing.php
* @type : Admin Page
* @scope : Module Only
* @allowed : AI image generation testing, DALL·E 3 integration, image creation
* @reusability : Single Use
* @notes : AI image generation testing interface for thinker module
*/
if (!defined('ABSPATH')) exit;
// Component render functions are loaded centrally via global-layout.php
// Helper function to read last N lines of a file
function tail_file($filepath, $lines = 10) {
if (!file_exists($filepath)) {
return [];
}
$handle = fopen($filepath, "r");
if (!$handle) {
return [];
}
$linecounter = $lines;
$pos = -2;
$beginning = false;
$text = [];
while ($linecounter > 0) {
$t = " ";
while ($t != "\n") {
if (fseek($handle, $pos, SEEK_END) == -1) {
$beginning = true;
break;
}
$t = fgetc($handle);
$pos--;
}
$linecounter--;
if ($beginning) {
rewind($handle);
}
$text[$lines - $linecounter - 1] = fgets($handle);
if ($beginning) break;
}
fclose($handle);
return array_reverse($text);
}
// Image Testing content
ob_start();
// Handle Form Submit
$image_url = '';
$response_error = '';
$save_path = '';
$generated_image_data = null;
// Handle saving prompt settings
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_prompt_nonce']) && wp_verify_nonce($_POST['save_prompt_nonce'], 'save_prompt')) {
$prompt_template = sanitize_textarea_field(wp_unslash($_POST['prompt_template']));
update_option('igny8_image_prompt_template', $prompt_template);
$settings_saved = true;
}
// Handle image generation
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['generate_image_nonce']) && wp_verify_nonce($_POST['generate_image_nonce'], 'generate_image')) {
$title = sanitize_text_field(wp_unslash($_POST['title']));
$desc = sanitize_textarea_field(wp_unslash($_POST['desc']));
$image_type = sanitize_text_field(wp_unslash($_POST['image_type']));
$image_provider = sanitize_text_field(wp_unslash($_POST['image_provider'] ?? 'openai'));
$negative_prompt = sanitize_textarea_field(wp_unslash($_POST['negative_prompt'] ?? 'text, watermark, logo, overlay, title, caption, writing on walls, writing on objects, UI, infographic elements, post title'));
$image_width = intval($_POST['image_width'] ?? 1024);
$image_height = intval($_POST['image_height'] ?? 1024);
$image_format = sanitize_text_field(wp_unslash($_POST['image_format'] ?? 'jpg'));
// Get custom prompt template or use default
$prompt_template = wp_unslash(get_option('igny8_image_prompt_template', 'Generate a {image_type} image for a blog post titled "{title}". Description: {description}'));
// Replace placeholders in prompt template
$prompt = str_replace(
['{image_type}', '{title}', '{description}'],
[$image_type, $title, $desc],
$prompt_template
);
// Get API keys
$openai_key = get_option('igny8_api_key', '');
$runware_key = get_option('igny8_runware_api_key', '');
// Check if the required API key is configured
$required_key = ($image_provider === 'runware') ? $runware_key : $openai_key;
$service_name = ($image_provider === 'runware') ? 'Runware' : 'OpenAI';
if (empty($required_key)) {
$response_error = $service_name . ' API key not configured. Please set your API key in the settings.';
} else {
// Debug: Log the request details
error_log('Igny8 Image Generation - Starting request');
error_log('Igny8 Image Generation - Prompt: ' . $prompt);
error_log('Igny8 Image Generation - Provider: ' . $image_provider);
error_log('Igny8 Image Generation - API Key: ' . substr($required_key, 0, 10) . '...');
try {
if ($image_provider === 'runware') {
// Runware API Call
error_log('Igny8 Image Generation - Using Runware service');
// Prepare Runware API payload
$payload = [
[
'taskType' => 'authentication',
'apiKey' => $runware_key
],
[
'taskType' => 'imageInference',
'taskUUID' => wp_generate_uuid4(),
'positivePrompt' => $prompt,
'negativePrompt' => $negative_prompt,
'model' => 'runware:97@1',
'width' => $image_width,
'height' => $image_height,
'steps' => 30,
'CFGScale' => 7.5,
'numberResults' => 1,
'outputFormat' => $image_format
]
];
// Make API request
$response = wp_remote_post('https://api.runware.ai/v1', [
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode($payload),
'timeout' => 60
]);
if (is_wp_error($response)) {
error_log('Igny8 Image Generation - Runware Error: ' . $response->get_error_message());
$response_error = 'Runware API Error: ' . $response->get_error_message();
} else {
$response_body = wp_remote_retrieve_body($response);
$response_data = json_decode($response_body, true);
if (isset($response_data['data'][0]['imageURL'])) {
// Download and save the image
$image_url = $response_data['data'][0]['imageURL'];
$filename = sanitize_file_name($title) . '_' . time() . '.' . $image_format;
// Create uploads directory
$upload_dir = wp_upload_dir();
$igny8_dir = $upload_dir['basedir'] . '/igny8-ai-images/';
if (!file_exists($igny8_dir)) {
wp_mkdir_p($igny8_dir);
}
// Download image
$image_response = wp_remote_get($image_url);
if (is_wp_error($image_response)) {
$response_error = 'Failed to download Runware image: ' . $image_response->get_error_message();
} else {
$image_data = wp_remote_retrieve_body($image_response);
$file_path = $igny8_dir . $filename;
$saved = file_put_contents($file_path, $image_data);
if ($saved === false) {
$response_error = 'Failed to save Runware image file';
} else {
$response_success = true;
$response_image_url = str_replace(ABSPATH, home_url('/'), $file_path);
$response_message = 'Image generated successfully using Runware!';
// Set generated image data for consistent display
$generated_image_data = [
'model' => 'runware:97@1',
'provider' => 'runware',
'negative_prompt' => $negative_prompt
];
error_log('Igny8 Image Generation - Runware image saved to: ' . $file_path);
}
}
} elseif (isset($response_data['errors'][0]['message'])) {
$response_error = 'Runware API Error: ' . $response_data['errors'][0]['message'];
} else {
$response_error = 'Unknown response from Runware API';
}
}
} else {
// OpenAI DALL-E 3 API Call
error_log('Igny8 Image Generation - Using OpenAI DALL-E 3 service');
$data = [
'model' => 'dall-e-3',
'prompt' => $prompt,
'n' => 1,
'size' => $image_width . 'x' . $image_height
];
// Add negative prompt if supported (DALL-E 3 doesn't support negative prompts, but we'll log it)
if (!empty($negative_prompt)) {
error_log('Igny8 Image Generation - Negative prompt provided but DALL-E 3 does not support negative prompts: ' . $negative_prompt);
}
$args = [
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $openai_key,
'OpenAI-Beta' => 'assistants=v2'
],
'body' => json_encode($data),
'timeout' => 60,
'sslverify' => true
];
error_log('Igny8 Image Generation - Making API request to OpenAI');
$api_response = wp_remote_post('https://api.openai.com/v1/images/generations', $args);
// Debug: Log response details
if (is_wp_error($api_response)) {
error_log('Igny8 Image Generation - WP Error: ' . $api_response->get_error_message());
error_log('Igny8 Image Generation - Error Code: ' . $api_response->get_error_code());
$response_error = 'WordPress HTTP Error: ' . $api_response->get_error_message();
} else {
$response_code = wp_remote_retrieve_response_code($api_response);
$response_body = wp_remote_retrieve_body($api_response);
error_log('Igny8 Image Generation - Response Code: ' . $response_code);
error_log('Igny8 Image Generation - Response Body: ' . substr($response_body, 0, 500));
if ($response_code === 200) {
$body = json_decode($response_body, true);
if (isset($body['data'][0]['url'])) {
$image_url = $body['data'][0]['url'];
$generated_image_data = $body['data'][0];
error_log('Igny8 Image Generation - Image URL received: ' . $image_url);
// Log successful image generation
global $wpdb;
$wpdb->insert($wpdb->prefix . 'igny8_logs', [
'level' => 'info',
'message' => 'Image generation successful',
'context' => wp_json_encode([
'model' => 'dall-e-3',
'prompt_length' => strlen($prompt),
'image_size' => '1024x1024',
'total_cost' => 0.040, // DALL-E 3 standard cost
'title' => $title,
'image_type' => $image_type
]),
'source' => 'openai_image',
'status' => 'success',
'api_id' => $body['data'][0]['id'] ?? null,
'user_id' => get_current_user_id(),
'created_at' => current_time('mysql')
], ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s']);
// Try to save to plugin directory first, fallback to WordPress uploads
$plugin_upload_dir = plugin_dir_path(__FILE__) . '../../assets/ai-images/';
$wp_upload_dir = wp_upload_dir();
$wp_upload_path = $wp_upload_dir['basedir'] . '/igny8-ai-images/';
$slug = sanitize_title($title);
$upload_dir = $plugin_upload_dir;
$folder = $upload_dir . $slug . '/';
$save_location = 'plugin';
// Check if plugin directory is writable
if (!file_exists($plugin_upload_dir)) {
if (wp_mkdir_p($plugin_upload_dir)) {
error_log('Igny8 Image Generation - Created plugin directory: ' . $plugin_upload_dir);
} else {
error_log('Igny8 Image Generation - Failed to create plugin directory: ' . $plugin_upload_dir);
// Fallback to WordPress uploads directory
$upload_dir = $wp_upload_path;
$folder = $upload_dir . $slug . '/';
$save_location = 'wordpress';
error_log('Igny8 Image Generation - Using WordPress uploads as fallback: ' . $wp_upload_path);
}
}
if (file_exists($plugin_upload_dir) && !is_writable($plugin_upload_dir)) {
// Try to fix permissions
chmod($plugin_upload_dir, 0755);
error_log('Igny8 Image Generation - Attempted to fix plugin directory permissions: ' . $plugin_upload_dir);
if (!is_writable($plugin_upload_dir)) {
// Fallback to WordPress uploads directory
$upload_dir = $wp_upload_path;
$folder = $upload_dir . $slug . '/';
$save_location = 'wordpress';
error_log('Igny8 Image Generation - Plugin directory not writable, using WordPress uploads: ' . $wp_upload_path);
}
}
// Ensure target directory exists
if (!file_exists($folder)) {
wp_mkdir_p($folder);
error_log('Igny8 Image Generation - Created directory: ' . $folder);
}
// Final check if directory is writable
if (file_exists($folder) && !is_writable($folder)) {
// Try to fix permissions one more time
chmod($folder, 0755);
error_log('Igny8 Image Generation - Attempted to fix final permissions for: ' . $folder);
if (!is_writable($folder)) {
$response_error = 'Directory is not writable. Please check file permissions for: ' . $folder;
error_log('Igny8 Image Generation - Directory still not writable: ' . $folder);
error_log('Igny8 Image Generation - Directory permissions: ' . substr(sprintf('%o', fileperms($folder)), -4));
}
} elseif (!file_exists($folder)) {
$response_error = 'Directory does not exist and could not be created: ' . $folder;
error_log('Igny8 Image Generation - Directory does not exist: ' . $folder);
}
if (empty($response_error)) {
// Download image data
$image_data = file_get_contents($image_url);
if ($image_data === false) {
$response_error = 'Failed to download image from URL: ' . $image_url;
error_log('Igny8 Image Generation - Failed to download image from: ' . $image_url);
} else {
$filename = $folder . 'image.png';
// Try to write the file
$bytes_written = file_put_contents($filename, $image_data);
if ($bytes_written === false) {
$response_error = 'Failed to save image file. Check file permissions.';
error_log('Igny8 Image Generation - Failed to write file: ' . $filename);
error_log('Igny8 Image Generation - Directory permissions: ' . substr(sprintf('%o', fileperms($folder)), -4));
error_log('Igny8 Image Generation - Directory writable: ' . (is_writable($folder) ? 'yes' : 'no'));
} else {
// Determine the correct path for display
if ($save_location === 'wordpress') {
$display_path = '/wp-content/uploads/igny8-ai-images/' . $slug . '/image.png';
$log_path = $display_path;
} else {
$display_path = '/assets/ai-images/' . $slug . '/image.png';
$log_path = $display_path;
}
$save_path = 'Saved to: ' . $display_path . ' (' . $bytes_written . ' bytes)';
error_log('Igny8 Image Generation - Image saved successfully: ' . $filename . ' (' . $bytes_written . ' bytes)');
error_log('Igny8 Image Generation - Save location: ' . $save_location);
// Update log with file path
$wpdb->update(
$wpdb->prefix . 'igny8_logs',
[
'context' => wp_json_encode([
'model' => 'dall-e-3',
'prompt_length' => strlen($prompt),
'image_size' => '1024x1024',
'total_cost' => 0.040,
'title' => $title,
'image_type' => $image_type,
'file_path' => $log_path,
'file_size' => $bytes_written,
'save_location' => $save_location
])
],
['api_id' => $body['data'][0]['id'] ?? null],
['%s'],
['%s']
);
}
}
}
} else {
$response_error = 'Image URL not returned from API. Response: ' . substr($response_body, 0, 200);
if (isset($body['error']['message'])) {
$response_error .= ' Error: ' . $body['error']['message'];
}
// Log failed image generation
global $wpdb;
$wpdb->insert($wpdb->prefix . 'igny8_logs', [
'level' => 'error',
'message' => 'Image generation failed - no URL returned',
'context' => wp_json_encode([
'model' => 'dall-e-3',
'prompt_length' => strlen($prompt),
'image_size' => '1024x1024',
'error_response' => substr($response_body, 0, 500),
'title' => $title,
'image_type' => $image_type
]),
'source' => 'openai_image',
'status' => 'error',
'user_id' => get_current_user_id(),
'created_at' => current_time('mysql')
], ['%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s']);
}
} else {
$response_error = 'API returned error code ' . $response_code . '. Response: ' . substr($response_body, 0, 200);
// Log failed image generation
global $wpdb;
$wpdb->insert($wpdb->prefix . 'igny8_logs', [
'level' => 'error',
'message' => 'Image generation failed - HTTP error',
'context' => wp_json_encode([
'model' => 'dall-e-3',
'prompt_length' => strlen($prompt),
'image_size' => '1024x1024',
'http_code' => $response_code,
'error_response' => substr($response_body, 0, 500),
'title' => $title,
'image_type' => $image_type
]),
'source' => 'openai_image',
'status' => 'error',
'user_id' => get_current_user_id(),
'created_at' => current_time('mysql')
], ['%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s']);
}
}
} // End of OpenAI else block
} catch (Exception $e) {
error_log('Igny8 Image Generation - Exception: ' . $e->getMessage());
$response_error = 'Exception occurred: ' . $e->getMessage();
// Log exception
global $wpdb;
$wpdb->insert($wpdb->prefix . 'igny8_logs', [
'level' => 'error',
'message' => 'Image generation failed - exception',
'context' => wp_json_encode([
'model' => 'dall-e-3',
'prompt_length' => strlen($prompt),
'image_size' => '1024x1024',
'exception_message' => $e->getMessage(),
'title' => $title,
'image_type' => $image_type
]),
'source' => 'openai_image',
'status' => 'error',
'user_id' => get_current_user_id(),
'created_at' => current_time('mysql')
], ['%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s']);
}
}
}
?>
<div class="igny8-module-home">
<!-- Page Header -->
<div class="igny8-dashboard-section">
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>AI Image Generation Test</h3>
<p class="igny8-card-subtitle">Test OpenAI DALL·E 3 image generation with your content data</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-format-image igny8-dashboard-icon-lg igny8-dashboard-icon-purple"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<p>Generate AI-powered images for your content using OpenAI's DALL·E 3. Enter your post details below to create custom images based on your title, description, keywords, and cluster information.</p>
</div>
</div>
</div>
<div class="igny8-grid-3">
<!-- Image Generation Form -->
<div class="igny8-dashboard-section">
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Generate Image</h3>
<p class="igny8-card-subtitle">Configure image generation parameters</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-generic igny8-dashboard-icon-lg igny8-dashboard-icon-blue"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<form method="POST" id="igny8-image-generation-form">
<?php wp_nonce_field('generate_image', 'generate_image_nonce'); ?>
<div class="igny8-form-group">
<label for="title">Post Title *</label>
<input type="text" id="title" name="title" class="igny8-form-control" value="<?php echo isset($_POST['title']) ? esc_attr(wp_unslash($_POST['title'])) : ''; ?>" required>
</div>
<div class="igny8-form-group">
<label for="desc">Prompt Description *</label>
<textarea id="desc" name="desc" rows="4" class="igny8-form-control" placeholder="Describe the image you want to generate..." required><?php echo isset($_POST['desc']) ? esc_textarea(wp_unslash($_POST['desc'])) : ''; ?></textarea>
<small class="form-help">Describe the visual elements, style, mood, and composition you want in the image.</small>
</div>
<div class="igny8-form-group">
<label for="negative_prompt">Negative Prompt</label>
<textarea id="negative_prompt" name="negative_prompt" rows="2" class="igny8-form-control" placeholder="Describe what you DON'T want in the image..."><?php echo isset($_POST['negative_prompt']) ? esc_textarea(wp_unslash($_POST['negative_prompt'])) : 'text, watermark, logo, overlay, title, caption, writing on walls, writing on objects, UI, infographic elements, post title'; ?></textarea>
<small class="form-help">Specify elements to avoid in the generated image (text, watermarks, logos, etc.).</small>
</div>
<div class="igny8-form-group">
<label for="image_type">Image Type</label>
<select id="image_type" name="image_type" class="igny8-form-control">
<option value="realistic" <?php selected(isset($_POST['image_type']) ? $_POST['image_type'] : 'realistic', 'realistic'); ?>>Realistic</option>
<option value="illustration" <?php selected(isset($_POST['image_type']) ? $_POST['image_type'] : '', 'illustration'); ?>>Illustration</option>
<option value="3D render" <?php selected(isset($_POST['image_type']) ? $_POST['image_type'] : '', '3D render'); ?>>3D Render</option>
<option value="minimalist" <?php selected(isset($_POST['image_type']) ? $_POST['image_type'] : '', 'minimalist'); ?>>Minimalist</option>
<option value="cartoon" <?php selected(isset($_POST['image_type']) ? $_POST['image_type'] : '', 'cartoon'); ?>>Cartoon</option>
</select>
</div>
<div class="igny8-form-group">
<label for="image_provider">Image Provider</label>
<select id="image_provider" name="image_provider" class="igny8-form-control">
<option value="openai" <?php selected(isset($_POST['image_provider']) ? $_POST['image_provider'] : 'openai', 'openai'); ?>>OpenAI DALL·E 3</option>
<option value="runware" <?php selected(isset($_POST['image_provider']) ? $_POST['image_provider'] : '', 'runware'); ?>>Runware HiDream-I1 Full</option>
</select>
<small class="form-help">Choose the AI image generation service to use.</small>
</div>
<div class="igny8-form-group">
<label for="igny8_image_size_selector">Image Size</label>
<select id="igny8_image_size_selector" name="image_size" class="igny8-form-control">
<!-- Options will be populated dynamically by JavaScript -->
</select>
<small class="form-help">Choose the image dimensions for your generated image.</small>
<div id="igny8-selected-dimensions" style="margin-top: 5px; font-weight: bold; color: #0073aa;"></div>
</div>
<div class="igny8-form-group">
<label for="igny8_image_format_selector">Image Format</label>
<select id="igny8_image_format_selector" name="image_format" class="igny8-form-control">
<option value="jpg" <?php selected(isset($_POST['image_format']) ? $_POST['image_format'] : 'jpg', 'jpg'); ?>>JPG</option>
<option value="png" <?php selected(isset($_POST['image_format']) ? $_POST['image_format'] : '', 'png'); ?>>PNG</option>
<option value="webp" <?php selected(isset($_POST['image_format']) ? $_POST['image_format'] : '', 'webp'); ?>>WEBP</option>
</select>
<small class="form-help">Choose the image file format for your generated image.</small>
<div id="igny8-selected-format" style="margin-top: 5px; font-weight: bold; color: #0073aa;"></div>
</div>
<!-- Hidden fields for width and height -->
<input type="hidden" id="image_width" name="image_width" value="1024">
<input type="hidden" id="image_height" name="image_height" value="1024">
<div class="igny8-form-actions">
<button type="submit" class="igny8-btn igny8-btn-primary" id="generate-btn">
<span class="dashicons dashicons-format-image"></span>
<span class="btn-text">Generate Image</span>
<span class="btn-loading" style="display: none;">
<span class="dashicons dashicons-update" style="animation: spin 1s linear infinite;"></span>
Sending Request...
</span>
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Results Panel -->
<div class="igny8-dashboard-section">
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Generated Image</h3>
<p class="igny8-card-subtitle">AI-generated image results</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-format-image igny8-dashboard-icon-lg igny8-dashboard-icon-green"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<?php if ($image_url): ?>
<div class="igny8-image-result">
<div class="igny8-image-preview">
<img src="<?php echo esc_url($image_url); ?>" alt="Generated Image" style="max-width: 100%; height: auto; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15);">
</div>
<?php if ($generated_image_data): ?>
<div class="igny8-image-details">
<h4>Image Details</h4>
<ul class="igny8-details-list">
<li><strong>Size:</strong> <?php echo $image_width . 'x' . $image_height; ?> pixels</li>
<li><strong>Format:</strong> <?php echo strtoupper($image_format); ?></li>
<li><strong>Model:</strong> <?php echo ($image_provider === 'runware') ? 'Runware HiDream-I1 Full' : 'DALL·E 3'; ?></li>
<?php if (isset($generated_image_data['revised_prompt'])): ?>
<li><strong>Revised Prompt:</strong> <?php echo esc_html($generated_image_data['revised_prompt']); ?></li>
<?php endif; ?>
<?php if (!empty($negative_prompt)): ?>
<li><strong>Negative Prompt:</strong> <?php echo esc_html($negative_prompt); ?></li>
<?php endif; ?>
</ul>
</div>
<?php endif; ?>
<?php if ($save_path): ?>
<div class="igny8-save-info">
<p class="igny8-success-message">
<span class="dashicons dashicons-yes-alt" style="color: var(--green);"></span>
<?php echo esc_html($save_path); ?>
</p>
</div>
<?php endif; ?>
<div class="igny8-image-actions">
<a href="<?php echo esc_url($image_url); ?>" target="_blank" class="igny8-btn igny8-btn-outline">
<span class="dashicons dashicons-external"></span>
View Original
</a>
<button type="button" class="igny8-btn igny8-btn-success" onclick="navigator.clipboard.writeText('<?php echo esc_js($image_url); ?>')">
<span class="dashicons dashicons-admin-page"></span>
Copy URL
</button>
</div>
</div>
<?php elseif ($response_error): ?>
<div class="igny8-error-message">
<span class="dashicons dashicons-warning" style="color: var(--red); font-size: 24px;"></span>
<h4>Generation Failed</h4>
<p><?php echo esc_html($response_error); ?></p>
</div>
<?php else: ?>
<div class="igny8-empty-state">
<span class="dashicons dashicons-format-image" style="color: var(--text-dim); font-size: 48px;"></span>
<p>No image generated yet. Fill out the form and click "Generate Image" to create your first AI image.</p>
</div>
<?php endif; ?>
</div>
</div>
</div>
<!-- Prompt Settings -->
<div class="igny8-dashboard-section">
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Prompt Settings</h3>
<p class="igny8-card-subtitle">Customize your image generation prompt template</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-edit-page igny8-dashboard-icon-lg igny8-dashboard-icon-amber"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<?php if (isset($settings_saved)): ?>
<div class="igny8-notice igny8-notice-success">
<span class="dashicons dashicons-yes-alt"></span>
Prompt template saved successfully!
</div>
<?php endif; ?>
<form method="POST">
<?php wp_nonce_field('save_prompt', 'save_prompt_nonce'); ?>
<div class="igny8-form-group">
<label for="prompt_template">Prompt Template</label>
<textarea id="prompt_template" name="prompt_template" rows="6" class="igny8-form-control" placeholder="Enter your custom prompt template..."><?php echo esc_textarea(wp_unslash(get_option('igny8_image_prompt_template', 'Generate a {image_type} image for a blog post titled "{title}". Description: {description}'))); ?></textarea>
<small class="form-help">
<strong>Available placeholders:</strong><br>
<code>{title}</code> - Post title<br>
<code>{description}</code> - Prompt description<br>
<code>{image_type}</code> - Selected image type
</small>
</div>
<div class="igny8-form-actions">
<button type="submit" class="igny8-btn igny8-btn-success">
<span class="dashicons dashicons-saved"></span>
Save Prompt Template
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- API Configuration Info -->
<div class="igny8-dashboard-section">
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>API Configuration</h3>
<p class="igny8-card-subtitle">OpenAI API settings and requirements</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-settings igny8-dashboard-icon-lg igny8-dashboard-icon-amber"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<div class="igny8-api-info">
<div class="igny8-info-grid">
<div class="igny8-info-item">
<h4>OpenAI API Key</h4>
<p>Your OpenAI API key is <?php echo !empty(get_option('igny8_api_key', '')) ? '<span class="igny8-status-ok">configured</span>' : '<span class="igny8-status-error">not set</span>'; ?></p>
<a href="<?php echo admin_url('admin.php?page=igny8-settings'); ?>" class="igny8-btn igny8-btn-outline igny8-btn-sm">
<span class="dashicons dashicons-admin-settings"></span>
Configure API Key
</a>
</div>
<div class="igny8-info-item">
<h4>Image Service</h4>
<p><?php
$current_provider = isset($image_provider) ? $image_provider : 'openai';
if ($current_provider === 'runware') {
echo 'Runware HiDream-I1 Full';
} else {
echo 'OpenAI DALL·E 3';
}
?> (1024x1024 resolution)</p>
</div>
<div class="igny8-info-item">
<h4>Image Storage</h4>
<p>Generated images are saved to <code>/assets/ai-images/[slug]/image.png</code></p>
</div>
<div class="igny8-info-item">
<h4>Usage Limits</h4>
<p>Subject to your OpenAI account limits and billing</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Debug Information -->
<div class="igny8-dashboard-section">
<div class="igny8-card">
<div class="igny8-standard-header">
<div class="igny8-card-header-content">
<div class="igny8-card-title-text">
<h3>Debug Information</h3>
<p class="igny8-card-subtitle">Technical details and troubleshooting</p>
</div>
<div class="igny8-card-icon">
<span class="dashicons dashicons-admin-tools igny8-dashboard-icon-lg igny8-dashboard-icon-amber"></span>
</div>
</div>
</div>
<div class="igny8-card-body">
<div class="igny8-debug-info">
<div class="igny8-debug-grid">
<div class="igny8-debug-item">
<h4>Server Information</h4>
<ul class="igny8-debug-list">
<li><strong>PHP Version:</strong> <?php echo PHP_VERSION; ?></li>
<li><strong>WordPress Version:</strong> <?php echo get_bloginfo('version'); ?></li>
<li><strong>cURL Available:</strong> <?php echo function_exists('curl_init') ? 'Yes' : 'No'; ?></li>
<li><strong>SSL Support:</strong> <?php echo function_exists('openssl_version') ? 'Yes' : 'No'; ?></li>
<li><strong>Image Save Directory:</strong> <?php echo wp_upload_dir()['basedir'] . '/igny8-ai-images/'; ?></li>
<li><strong>Image Dir Writable:</strong>
<?php
$image_dir = wp_upload_dir()['basedir'] . '/igny8-ai-images/';
if (file_exists($image_dir)) {
echo is_writable($image_dir) ? '✅ Yes' : '❌ No (' . substr(sprintf('%o', fileperms($image_dir)), -4) . ')';
} else {
echo '❌ Directory does not exist';
}
?>
</li>
<li><strong>WordPress Uploads:</strong> <?php echo wp_upload_dir()['basedir']; ?></li>
<li><strong>WP Uploads Writable:</strong>
<?php
$wp_uploads = wp_upload_dir()['basedir'];
echo is_writable($wp_uploads) ? '✅ Yes' : '❌ No (' . substr(sprintf('%o', fileperms($wp_uploads)), -4) . ')';
?>
</li>
</ul>
</div>
<div class="igny8-debug-item">
<h4>API Configuration</h4>
<ul class="igny8-debug-list">
<li><strong>API Key Status:</strong> <?php echo !empty(get_option('igny8_api_key', '')) ? 'Configured' : 'Not Set'; ?></li>
<li><strong>API Key Length:</strong> <?php echo strlen(get_option('igny8_api_key', '')); ?> characters</li>
<li><strong>Prompt Template:</strong> <?php echo strlen(get_option('igny8_image_prompt_template', '')); ?> characters</li>
</ul>
</div>
<div class="igny8-debug-item">
<h4>Recent Errors</h4>
<div class="igny8-error-log">
<?php
$error_log = ini_get('error_log');
if ($error_log && file_exists($error_log)) {
$recent_errors = tail_file($error_log, 10);
if ($recent_errors) {
echo '<pre style="font-size: 11px; background: #f8f9fa; padding: 10px; border-radius: 4px; max-height: 200px; overflow-y: auto;">';
foreach ($recent_errors as $error) {
if (strpos($error, 'Igny8 Image Generation') !== false) {
echo esc_html($error) . "\n";
}
}
echo '</pre>';
} else {
echo '<p>No recent Igny8 errors found.</p>';
}
} else {
echo '<p>Error log not accessible.</p>';
}
?>
</div>
</div>
<div class="igny8-debug-item">
<h4>Test Connection</h4>
<button type="button" class="igny8-btn igny8-btn-outline" onclick="testConnection()">
<span class="dashicons dashicons-admin-network"></span>
Test API Connection
</button>
<div id="connection-test-result" style="margin-top: 10px;"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('igny8-image-generation-form');
const generateBtn = document.getElementById('generate-btn');
const btnText = generateBtn.querySelector('.btn-text');
const btnLoading = generateBtn.querySelector('.btn-loading');
if (form) {
form.addEventListener('submit', function(e) {
// Show loading state
btnText.style.display = 'none';
btnLoading.style.display = 'inline-flex';
generateBtn.disabled = true;
// Show notification that request is being sent
showNotification('Sending image generation request to DALL·E 3...', 'info');
});
}
// Use unified global notification system
function showNotification(message, type = 'info') {
if (typeof igny8GlobalNotification === 'function') {
igny8GlobalNotification(message, type);
} else {
// Fallback to basic alert if global system not available
alert(message);
}
}
// Show success/error notifications if page was just submitted
<?php if ($image_url): ?>
showNotification('Image generated successfully! 🎉', 'success');
<?php elseif ($response_error): ?>
showNotification('Image generation failed: <?php echo esc_js($response_error); ?>', 'error');
<?php endif; ?>
});
// Global function for testing API connection
function testConnection() {
const resultDiv = document.getElementById('connection-test-result');
resultDiv.innerHTML = '<p>Testing connection...</p>';
// Simple test request to check if server can make HTTP requests
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'action=igny8_test_connection&nonce=<?php echo wp_create_nonce('igny8_test_connection'); ?>'
})
.then(response => response.json())
.then(data => {
if (data.success) {
resultDiv.innerHTML = '<p style="color: green;">✓ Connection test successful</p>';
} else {
resultDiv.innerHTML = '<p style="color: red;">✗ Connection test failed: ' + (data.data || 'Unknown error') + '</p>';
}
})
.catch(error => {
resultDiv.innerHTML = '<p style="color: red;">✗ Connection test failed: ' + error.message + '</p>';
});
}
</script>
<?php
// Localize script for the image testing page
wp_localize_script('igny8-admin-js', 'IGNY8_PAGE', [
'module' => 'thinker',
'subpage' => 'image-testing',
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('igny8_image_testing_settings')
]);
?>

Some files were not shown because too many files have changed in this diff Show More