| Property | Value |
|---|---|
| Plugin ID | com.osticket:markdown-support |
| Name | Markdown Support |
| Plugin class | MarkdownPlugin in class.MarkdownPlugin.php |
| Configuration class | MarkdownConfig in config.php |
| Instances | Singleton: exactly one instance, enable() creates it if needed |
| PHP | 8.0 or newer: composer.json declares >=7.4, but ConfigCache uses the PHP 8.0 type mixed. On PHP 7.4 get() and set() raise a TypeError |
| Dependency | erusev/parsedown ^1.7 (locked to 1.7.4) |
The plugin needs two interventions in osTicket:
markdown to the static property ThreadEntryBody::$types. No file change is needed for this.include/class.thread.php it inserts the branch case 'markdown': return new MarkdownThreadEntryBody(...) after the case 'html': branch of ThreadEntryBody::fromFormattedText(). If it cannot find the pattern (a different osTicket version), it logs Unable to apply patch - pattern not found to the PHP error log and does not patch.If the file already contains a case 'markdown':, the plugin does nothing. Before patching, it copies the file to include/class.thread.php.markdown-backup if no backup exists yet. On deactivation it restores the file from this backup and deletes it. If the backup is missing, it restores nothing. An existing backup is not overwritten (see Installation, section Updating osTicket).
MarkdownThreadEntryBody extends ThreadEntryBody with the type markdown and renders in display():
Parsedown with setSafeMode(true) converts Markdown to HTML.Format::sanitize() cleans the result.pdf mode the plugin replaces img tags with an http(s) source with the text [Image]. In email mode the HTML stays unchanged.Search indexing (getSearchable()) converts the rendered HTML to text with Format::html2text().
osTicket uses the raw POST text for emails. The Markdown editor sends plain text, so emails would otherwise show Markdown syntax. The plugin proceeds as follows:
format=markdown it saves the fields response, note and message, renders them to HTML with MarkdownThreadEntryBody and sets format to html for osTicket.threadentry.created remembers the ID of the entry.format = 'markdown' back to THREAD_ENTRY_TABLE via SQL at the end of the request.In addition, the handler corrects the format via threadentry.created when the request carries a valid format (see API integration).
object.view: For a Ticket the plugin outputs the configuration block window.osTicketMarkdownConfig (defaultFormat, allowFormatSwitch, showToolbar, previewApiUrl), the stylesheet and the script js/markdown-editor.js. The files get ?v=<installed version> as a cache parameter.tickets.php or ticket.php (and ajax.php URLs containing ticket) the plugin also starts an output buffer that inserts the same assets before </head>. A check for window.osTicketMarkdownConfig prevents double insertion.markdown-preview.php is copied to api/markdown-preview.php on activation (and on every start if the file differs), because include/ is blocked by .htaccess. The endpoint:
POST with JSON {"markdown": "..."} and answers with {"success": true, "html": "..."},javascript: and data: values in href/src, on* attributes, style with expression() and script tags,markdown field,vendor/autoload.php next to the file or at ../include/plugins/markdown-support/vendor/autoload.php.The endpoint exists at two paths: as
api/markdown-preview.php(copied there by the plugin) and as the original in the plugin folderinclude/plugins/markdown-support/markdown-preview.php, which the plugin's.htaccessopens. Both paths are reachable without login, check no CSRF token, do not limit the size of the input and do not load osTicket. The editor's live preview uses only the copy inapi/.
The preview does not pass through
Format::sanitize(), only through its own filter. It therefore does not always show exactly the HTML that later appears in the ticket view.
| Measure | Where |
|---|---|
| Parsedown SafeMode (HTML in the text is escaped, unsafe URL schemes are neutralized) | Rendering and preview |
Format::sanitize() (osTicket) |
Rendering of thread entries |
Regex filter for javascript:/data: URLs, on*, expression(), script |
Preview endpoint |
Image upload: MIME whitelist (JPEG, PNG, GIF, WebP, BMP), URL check, alt text without [](), abort without CSRF token |
Editor in the browser |
Own .htaccess in the plugin folder: allows markdown-preview.php, blocks *.log |
Plugin folder |
SQL values via db_input() |
Format and body updates |
The class MarkdownSanitizer is included with tests but is not called in the plugin's runtime flow.
include/.htaccessOn activation the plugin inserts two blocks: a FilesMatch block that allows js, css, map, json, images and fonts, and a block for PHP files under plugins/. On deactivation it removes both blocks again. There is no automatic adjustment for NGINX.
Apache matches the rule for PHP files (<FilesMatch "^plugins/.+\.php$">) only against file names without a path, so it probably never matches. markdown-preview.php is opened by the Files directive in the plugin folder's .htaccess.
markdown-support/
βββ plugin.php # plugin metadata
βββ class.MarkdownPlugin.php # plugin class, signals, enable/disable
βββ config.php # MarkdownConfig with the options
βββ markdown-body.php # MarkdownThreadEntryBody
βββ markdown-preview.php # preview endpoint (a copy goes to api/)
βββ MarkdownDetector.php # Markdown syntax detection
βββ .htaccess # allows the endpoint, blocks logs
βββ src/
β βββ Config/ConfigCache.php
β βββ Core/CorePatcher.php
β βββ Asset/AssetInjector.php
β βββ Asset/AssetDeployer.php
β βββ Http/MarkdownPostProcessor.php
β βββ Signal/ThreadEntryHandler.php
β βββ Format/ # sanitizer and interfaces
βββ css/markdown-editor.css
βββ js/markdown-editor.js # bundled from js/src/
βββ i18n/de_DE/ # German translation of the configuration
βββ vendor/ # Parsedown (Composer)
The repository additionally contains tests (PHPUnit), build sources for the JavaScript and documentation. CI runs a syntax check (php -l) with PHP 7.4 to 8.3.