Markus Michalski FAQ

Subticket Manager: Technical Details

Technical details of the osTicket Subticket Manager plugin: startup sequence, auto-linking, file structure, database changes, deployment to scp/, safeguards and limitations.

Deutsche Version

Back to overview

Subticket Manager: Technical Details

Plugin metadata

Property Value
Plugin ID subticket-manager
Name Subticket Manager
Plugin class SubticketPlugin in class.SubticketPlugin.php
Configuration class SubticketPluginConfig in config.php
Instances Singleton, enable() creates the instance automatically
License GNU GPL v2 (file LICENSE)

Startup sequence

On every page request where osTicket loads the active plugin, bootstrap() runs in this order:

graph TD
    A["bootstrap()"] --> B["Create services"]
    B --> C["Copy configuration to cache"]
    C --> D["Store subticket_parent from URL in session"]
    D --> E["Re-copy scp files if version differs"]
    E --> F["Create tables, column and indexes"]
    F --> G["Register staff app Subtickets"]
    G --> H["Signal object.view in staff area only"]
    H --> I["Signals model.updated and model.created"]
    I --> J["Queue indicator script on queue pages"]

Auto-linking on creation

sequenceDiagram
    participant A as Agent
    participant P as Panel
    participant O as osTicket
    participant H as TicketEventHandler
    A->>P: Create Subticket
    P->>O: New ticket form with parent ID
    O->>H: bootstrap stores ID in session
    A->>O: Create ticket
    O->>H: Signal model.created
    H->>O: UPDATE ost_ticket SET ticket_pid

The handler only accepts the ID if it is numeric and greater than 0. On creation it sets ticket_pid with a direct UPDATE, without existence, loop or department checks, and then clears the ID from the session.

File structure

subticket-manager/
β”œβ”€β”€ plugin.php                      # metadata
β”œβ”€β”€ class.SubticketPlugin.php       # plugin class, lifecycle, signal wiring
β”œβ”€β”€ config.php                      # configuration form
β”œβ”€β”€ queue-decoration.php            # ParentTicketDecoration
β”œβ”€β”€ ajax/
β”‚   β”œβ”€β”€ SubticketController.php     # logic behind ajax-subticket.php
β”‚   └── PluginPathResolver.php      # helper function for tests
β”œβ”€β”€ scp-files/                      # copied to scp/
β”‚   β”œβ”€β”€ subtickets.php              # overview page
β”‚   β”œβ”€β”€ ajax-subticket.php          # AJAX entry point
β”‚   └── apps.php                    # applications overview
β”œβ”€β”€ js/
β”‚   β”œβ”€β”€ subticket-panel.js          # panel interactions
β”‚   └── queue-indicator.js          # icons in the queue
└── src/                            # PSR-4, namespace SubticketManager
    β”œβ”€β”€ Asset/AssetDeployer.php     # copy and remove the scp files
    β”œβ”€β”€ Config/ConfigCache.php      # cache for configuration values
    β”œβ”€β”€ Database/DatabaseService.php # tables, column, indexes, foreign keys
    β”œβ”€β”€ Hierarchy/HierarchyService.php # link, unlink, loop check
    β”œβ”€β”€ Signal/TicketEventHandler.php  # signal handlers
    └── UI/PanelRenderer.php        # HTML, CSS and script loader of the panel

Database changes

The relationships themselves live in the osTicket column ost_ticket.ticket_pid. On startup the plugin adds, if not yet present:

Object Definition
Table ost_ticket_hierarchy_metadata ticket_id (primary key), auto_close_enabled, inherit_settings, dependency_type, max_children, created, updated
Table ost_ticket_progress parent_id (primary key), total_children, completed_children, in_progress_children, pending_children, last_calculated
Column ost_ticket.version int(11) DEFAULT 0
Index idx_ticket_pid on ost_ticket(ticket_pid)
Index idx_ticket_hierarchy on ost_ticket(ticket_id, ticket_pid, status_id)
Foreign keys fk_hierarchy_ticket, fk_progress_ticket on ost_ticket(ticket_id) with ON DELETE CASCADE, only if the table has no foreign key yet

The two tables and the version column are created by the plugin but never read or written. The plugin's features work with ticket_pid alone.

The checks run on every plugin start (that is, on every osTicket page request): as SHOW queries for tables, column and indexes, and per table as a SELECT on information_schema.TABLE_CONSTRAINTS for the foreign key. On uninstall the plugin removes the objects only if Remove Data on Uninstall is enabled (see Configuration).

Deployment to scp/

The AssetDeployer copies scp-files/subtickets.php, scp-files/ajax-subticket.php and scp-files/apps.php to scp/. It runs on enable() and on every start when the file scp/.subticket-deployed-version is missing or contains a different version than plugin.php. The target directory and write permission checks produce the error messages listed under Installation.

Rules and safeguards

Area Behavior
Linking ID greater than 0, no self-reference, both tickets must exist, no loop
Loop check isDescendant() follows the ticket_pid chain for at most ten levels. For deeper chains the check ends without a hit. There is no limit on hierarchy depth
Multiple parents Not possible, a ticket has exactly one ticket_pid column
Input Ticket IDs are validated as positive numbers, SQL values go through db_input()
CSRF link, unlink and create require a valid osTicket CSRF token
Department access Checked via canAccessDept(), administrators are exempt
Rate limit 100 requests per 60 seconds per session for children, link, unlink, create
Panel output Number, subject and status are output with htmlspecialchars

Compatibility check

The plugin class contains isCompatible(). The method returns false if the osTicket version is below 1.18.0, PHP is below 7.4 or the column ticket_pid is missing in ost_ticket.

Limitations