Priority Icons: Technical Details
How Priority Icons works: CSS and JavaScript injection via output buffer, text replacement in the browser, scanned areas, PJAX handling, accessibility and file structure.
Technical Details
Flow
graph TD
A["Page is requested"] --> B{"AJAX request?"}
B -->|"Yes"| C["No injection"]
B -->|"No"| D{"Option enabled off?"}
D -->|"Yes"| C
D -->|"No"| E["ob_start with callback"]
E --> F["Insert CSS, config and JS before end of head"]
F --> G["Browser: scan containers for priority text"]
G --> H["Replace text with icon"]
H --> I["pjax:end or MutationObserver"]
I -->|"New content"| G
Server side
- Plugin:
PriorityIconsPlugin(plugin idosticket:priority-icons, name "Priority Icons"), configuration classPriorityIconsConfig. - Singleton:
enable()automatically creates an instance when needed. bootstrap(): Nothing happens on AJAX requests (headerX-Requested-With: XMLHttpRequest). Nothing happens either when the optionenabledis off in the configuration of the first active instance. Otherwiseob_start()starts with the callbackinjectAssetsIntoOutput().- Callback: Inserts before
</head>(otherwise before</body>): a<style>, a<script>withwindow.PriorityIconsConfigand a<script>with the logic, all carrying the attributedata-plugin="priority-icons". If the output already contains this attribute, nothing is inserted twice. - Configuration for the script: The priority list with the colors from the plugin configuration, encoded with
JSON_HEX_TAG | JSON_HEX_APOS. - Inline instead of files: According to a code comment, the
.htaccessin osTicket'sinclude/folder blocks HTTP access to plugin files, which is why CSS and JavaScript are delivered inline. No core files are changed. - Why not
addExtraHeader()or signalapps.scp: According to a code comment,$ostis stillNULLinbootstrap(), andapps.scponly fires on the Apps page.
Client side (JavaScript)
- Scanned areas:
table.list.queue.tickets,.ticket_info,.ticket-info,#ticket-status,.pjax-container. If the script finds none of them, it scans the wholebody. - Matches: A text node is replaced when its trimmed text equals a priority name exactly (case-sensitive, 3 to 10 characters). Text nodes inside
script,style,textareaandinputare left alone. - Replacement: The text is replaced by a wrapper
.priority-wrapper. It contains an icon.priority-icon(circle, withtitleandaria-label="Priority: <name>") and the original text as.sr-only. The color is set on the icon via--priority-color. - Table cell: If the icon sits in a
td, the cell gets the attributedata-priority-celland a light background color. - Dynamic content: The script listens for
pjax:endvia jQuery (if jQuery is present) and watches thebodywith aMutationObserver. Rescans are debounced (50 to 100 ms). Already replaced elements are recognized bydata-priority-icon.
CSS
- Tooltip: Pure CSS from the
titleattribute (::after), arrow via::before. - Animation: The Emergency icon pulses; with
prefers-reduced-motion: reducethe animation is off. - Dark mode: With
prefers-color-scheme: dark, tooltip background and text color are swapped. - Print: Colors are preserved (
print-color-adjust: exact), tooltips and animation are dropped. - Unused: The stylesheet contains rules for
priority-icon--badgeandpriority-icon--dot. The script never sets these classes, so the rules have no effect.
Security
- The configuration is encoded with
JSON_HEX_TAG, so a</script>in a value cannot end the block. - Color values from the configuration are checked against
^#[0-9a-fA-F]{3,6}$. - Icons are created via the DOM API, not via
innerHTML. assets/.htaccessforbids PHP execution in theassets/folder.
File structure
The repository contains these files. Only plugin.php, class.PriorityIconsPlugin.php, config.php and assets/ are needed to run the plugin.
priority-icons/
+-- plugin.php # Plugin metadata
+-- class.PriorityIconsPlugin.php # Plugin class
+-- config.php # Configuration form
+-- assets/
| +-- .htaccess
| +-- priority-icons.css # Stylesheet
| +-- priority-icons.js # Replacement and PJAX handling
+-- README.md
+-- CHANGELOG.md
+-- CONTRIBUTING.md
+-- LICENSE
+-- docs/ # ARCHITECTURE.md, CODE_REVIEW.md
+-- tests/ # PHPUnit tests
+-- .github/workflows/ci.yml
+-- composer.json
+-- phpunit.xml
+-- .releaseinclude
+-- .gitignore