Search by

restruct / silverstripe-shortcodable

micschk

Provides a simple button to insert Shortcodes into the HTMLEditorField + an API for developers to define shortcodes

Package info

github.com/restruct/silverstripe-shortcodable

Type:silverstripe-vendormodule

pkg:composer/restruct/silverstripe-shortcodable

Fund package maintenance!

restruct

Statistics

Installs: 1 482

Dependents: 0

Suggesters: 0

Stars: 2

Open Issues: 2

5.1.0 2026-09-24 21:16 UTC

This package is auto-updated.

Last update: 2026-10-02 19:08:55 UTC


README

Maintained by Restruct. If this module saves you time, you can support ongoing maintenance.

Adds a button to HTMLEditorField for CMS users to insert Shortcodes in page content.
Shortcodes can optionally be represented in TinyMCE with a placeholder image.

This module is a partial-to-largely rewrite of sheadawson/silverstripe-shortcodable.
It depends on Silverstripe Simpler for some non-react UI functionalities (mainly the modal dialog).

Requirements

  • Silverstripe 5 or 6 (silverstripe/framework ^5 || ^6)
  • PHP 8.1 or newer (Silverstripe 6 itself needs 8.3)
  • restruct/silverstripe-simpler, installed automatically: its 0.x line (~0.2) on Silverstripe 5; on Silverstripe 6 it requires restruct/silverstripe-simpler 1.0.0 or later
  • Silverstripe 6 only: silverstripe/htmleditor-tinymce. TinyMCE is a separate module on Silverstripe 6 and silverstripe/recipe-cms does not include it. Without it the site still boots, but there is no TinyMCE editor to add the shortcode button to.

Installation

composer require restruct/silverstripe-shortcodable
# Silverstripe 6, if your project does not have it yet:
composer require silverstripe/htmleditor-tinymce

Version Compatibility

Branch Module Version Silverstripe PHP
main 5.x (from 5.1.0) ^5 || ^6 ^8.1
v4 4.x ^4 || ^5 ^7.4 || ^8.0

main is the maintained line and supports every Silverstripe version this module still targets. Silverstripe 4 reached end of life in April 2025 and is no longer supported or tested here; projects still on it can stay on the 4.x tags, which remain available. The 5.0.x tags declared Silverstripe 6 only, and could not be installed from Packagist (see CHANGELOG.md); use ^5.1.

Note: composer.json is the source of truth for exact version constraints.

Configuration

Register DataObjects or classes as shortcodable via Yaml config:

---
name: my_shortcodables
Only:
    classexists: Shortcodable\Shortcodable
---
Shortcodable\Shortcodable:
    shortcodable_classes:
        - My\Namespaced\Shortcodes\CurrentYearShortcode
        - My\Namespaced\Shortcodes\SomeOtherShortcode
        - ...
---

Configuration options

Option Default What it does
Shortcodable\Shortcodable.shortcodable_classes [] Classes to register with the shortcode parser and offer in the dialog.
Shortcodable\Shortcodable.htmleditor_names [cms] HTMLEditor configs that get the shortcode button. Only TinyMCE configs are changed; others are skipped.
Shortcodable\Controllers\ShortcodableAdminController.default_placeholder see class Size, font and colours of the default SVG placeholder (width, height, full_width, full_height, font, fontsize, fg, bg).

Per shortcodable class (as private static or YAML on that class):

Option Default What it does
shortcode short class name The shortcode tag, eg currentyear for [currentyear].
shortcode_callback parse_shortcode Name of the parser method.
shortcode_close_parent false Block-level output: close the wrapping element before the shortcode and reopen it after (see below). Also gives the default placeholder the full size.
placeholder_settings none width/height overriding the default placeholder size for this shortcode.

Required methods on shortcodable objects/classes

Implement these methods on your shortcodable classes (may also be added via an Extension):

(Required) shortcode parser callback:
public static function parse_shortcode(...)
OR:
public function MyCustomParser(...) combined with:
private static $shortcode_callback = 'MyCustomParser'

Parser method arguments: (see shortcode documentation)
($attrs, $content=null, $parser=null, $shortcode, $info)

NOTE: the parser method gets called on a singleton object instance.
(So there's no $this->ID or $this->owner->ID etc.)

Optional:

  • private static $shortcode (optional, else short ClassName will be used as [ClassName])
  • getShortcodeLabel() (optional nice description for in dropdown, singular_name() or else ClassName.Shortname will be used as fallback)
  • getShortcodeFields() (optional, attribute-formfields for popup)
  • getShortcodableRecords() (optional, if applied to DataObject)
  • getShortcodePlaceholder($attributes) (optional)

Wrapping (optional): In this module, $shortcodable_is_block and $disable_wrapper have been replaced with $shortcode_close_parent:

  • private static $shortcode_close_parent (optional, set to true if your shortcode output is a block-type html element)

If you set $shortcode_close_parent to true, the parent node will be closed before your shortcode output and reopened after.
Eg <p>Some [shortcode] content</p> would become <p>Some </p>[shortcode]<p> content</p>;

Shortcode placeholder images

To display a nice image instead of the raw shortcode in the CMS editor, implement a getShortcodePlaceHolder method on your shortcodable object:

/**
* Redirect to an image OR return image data directly to be displayed as shortcode placeholder in the editor
* (getShortcodePlaceHolder gets loaded as/from the 'src' attribute of an <img> tag)
*
* @param array $attributes attribute key-value pairs of the shortcode
* @return \SilverStripe\Control\HTTPResponse
**/
public function getShortcodePlaceHolder($attributes)
{
    // Flavour one: redirect to image URL (for this example we're also including the attributes array in the URL)
    Controller::curr()->redirect('https://www.silverstripe.org/apple-touch-icon-76x76.png?attrs='.json_encode($attributes));

    // Flavour two: output image/svg data directly (any bitmap but may also be SVG)
    // Hint: process attributes eg to show a set of image thumbnails wrapped in an SVG as gallery-placeholder
    $response = Controller::curr()->getResponse();
    $response->addHeader('Content-Type','image/svg+xml');
    $response->addHeader('Vary','Accept-Encoding');
    $response->setBody('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-code-square" viewBox="0 0 16 16">
      <path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
      <path d="M6.854 4.646a.5.5 0 0 1 0 .708L4.207 8l2.647 2.646a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 0 1 .708 0zm2.292 0a.5.5 0 0 0 0 .708L11.793 8l-2.647 2.646a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708 0z"/>
    </svg>');
    $response->output();
}

Default placeholder image

Without getShortcodePlaceHolder(), the editor shows an SVG generated by admin/shortcodable/placehold.img (CMS access required). It takes w and h (a positive number of pixels, or 100%), bg and fg (3 or 6 hex digits, no #), ff (font family), txtsize and txt. Invalid values fall back to the default_placeholder config.

Running the tests

The module cannot be tested on its own: it needs a host Silverstripe project. Require it there through a Composer path repository with symlink: true - /tests is export-ignore, so a dist or mirrored install contains no tests - add "Shortcodable\\Tests\\": "vendor/restruct/silverstripe-shortcodable/tests/" to the host's autoload-dev, then:

# Silverstripe 5 (PHPUnit 9) - the path must come before flush=1
vendor/bin/phpunit vendor/restruct/silverstripe-shortcodable/tests flush=1

# Silverstripe 6 (PHPUnit 11) - a flush=1 argument is ignored, use the env var
SS_PHPUNIT_FLUSH=1 vendor/bin/phpunit vendor/restruct/silverstripe-shortcodable/tests

On Silverstripe 6 the host also needs silverstripe/htmleditor-tinymce, or the TinyMCE button test is skipped. CI runs the suite against Silverstripe 5 and 6 on every push; see .github/workflows/ci.yml.

Status

  • TinyMCE button/plugin
  • Shortcode form in popup dialog
  • Custom/configurable shortcode independent of class name
  • Custom/configurable shortcode parser callback methods
  • Inserting new & editing existing shortcode (+undo)
  • Re-implement DataObjects as shortcodable (incl. getShortcodableRecords etc)
  • Re-implement placeholders
  • Check/re-implement(?) BOM fix and in ShortcodeController
  • Check/re-implement(?) P/DIV wrapper fix
  • Check/restore functionality for Uploadfields to load existing value
  • Check/implement(?) wrapping shortcodes

Refs: